using Newtonsoft.Json; using REST_JSON_API; using System.Collections; using System.Collections.Generic; using System.Linq; using TMPro; using UnityEngine; using UnityEngine.Networking; using static CommunicationEvents; public class ScrollDetails : MonoBehaviour { public static ScrollDetails Instance { get => _Instance; set { if (_Instance != value) Destroy(_Instance); _Instance = value; } } private static ScrollDetails _Instance; public WorldCursor cursor; public GameObject parameterDisplayPrefab; public static Scroll ActiveScroll { get; private set; } public GameObject mmtAnswerPopUp; private PopupBehavior Popup; public static List<RenderedScrollFact> ParameterDisplays { get; private set; } private static List<ScrollAssignment> LatestBackwartsCompletions; private static List<Fact> LatestRenderedHints; public string currentMmtAnswer; public bool DynamicScrollDescriptionsActive = true; public bool AutomaticHintGenerationActive = true; private bool SendingViewDone = true; private bool DynamicScrollInQue = false; private bool MagicInQue = false; private readonly IReadOnlyList<string> NoDynamicScroll = new List<string>() { // Insert ScrollURIS that are poorly optimized }; void Awake() { Instance = this; if (cursor == null) cursor = FindObjectOfType<WorldCursor>(); Popup = mmtAnswerPopUp.GetComponent<PopupBehavior>(); Popup.gameObject.SetActive(true); // force Awake } private void OnEnable() { ScrollFactHintEvent.AddListener(animateHint); NewAssignmentEvent.AddListener(NewAssignmentTrigger); } private void OnDisable() { ScrollFactHintEvent.RemoveListener(animateHint); NewAssignmentEvent.RemoveListener(NewAssignmentTrigger); } private void OnDestroy() { _Instance = null; } public void SetScroll(Scroll scroll_to_set) { ActiveScroll = scroll_to_set; Transform originalScroll = gameObject.transform.GetChild(1); Transform originalScrollView = originalScroll.GetChild(1); Transform originalViewport = originalScrollView.GetChild(0); //Clear all current ScrollFacts originalViewport.GetChild(0).gameObject.DestroyAllChildren(); originalScroll.GetChild(0).GetComponent<TextMeshProUGUI>().text = ActiveScroll.description; //ParameterDisplays.ForEach(gameObj => Destroy(gameObj)); ParameterDisplays = new(); for (int i = 0; i < ActiveScroll.requiredFacts.Count; i++) { GameObject originalObj = Instantiate(parameterDisplayPrefab, originalViewport.GetChild(0)); RenderedScrollFact originalRSF = originalObj.GetComponentInChildren<RenderedScrollFact>(); ParameterDisplays.Add(originalRSF); originalRSF.Populate(ActiveScroll, ActiveScroll.requiredFacts[i].@ref.uri); } if (StageStatic.stage.solution.ScrollOverwrites .TryGetValue(ActiveScroll.ScrollReference, out (string, int, bool)[] population) && population.Length > 0) { DynamicScrollInQue = true; // block update on population foreach ((string Id, int index, bool show) in population) { ParameterDisplays[index].URI = Id; ParameterDisplays[index].transform.parent.gameObject.SetActive(show); } DynamicScrollInQue = false; // unblock } NewAssignmentEvent.Invoke(); // init display } public bool SetNextEmptyTo(FactObjectUI activator) { RenderedScrollFact check = ParameterDisplays .Find(RSF => RSF != null && RSF.Payload != null && RSF.Payload.Equals(activator)); if (check != null) { check.URI = null; return false; } RenderedScrollFact empty = ParameterDisplays .Find(RSF => !RSF.IsSet); if (empty == null) return false; empty.SetByFactObject(activator); return true; } public void MagicButtonTrigger() { if (ActiveScroll == null) return; StartCoroutine(_MagicButton()); IEnumerator _MagicButton() { if (MagicInQue) yield break; // only need next in que to finish MagicInQue = true; while (!SendingViewDone || DynamicScrollInQue) yield return null; // Wait for last assignment MagicInQue = false; yield return SendView("/scroll/apply"); if (currentMmtAnswer == null) { Debug.LogError("Magic FAILED"); ScrollApplicationCheckingErrorEvent.Invoke(null); } else { if (VerboseURI) Debug.Log("Magic answers:\n" + currentMmtAnswer); System.DateTime serializeTime = System.DateTime.UtcNow; ScrollApplicationInfo pushout = JsonConvert.DeserializeObject<ScrollApplicationInfo>(currentMmtAnswer); Debug.LogFormat($"Answerd serialized in : {(System.DateTime.UtcNow - serializeTime).TotalMilliseconds}ms"); if (pushout.acquiredFacts.Count == 0 || pushout.errors.Length > 0) { ScrollApplicationCheckingErrorEvent.Invoke(pushout.errors); PushoutFactFailEvent.Invoke(); } else Popup.HidePopUp(); //close error Window yield return __GeneratePushoutFacts(pushout.acquiredFacts); } } IEnumerator __GeneratePushoutFacts(List<MMTFact> pushoutFacts) { List<Fact> new_facts = new(); Dictionary<string, string> old_to_new = new(); System.DateTime parseTime = System.DateTime.UtcNow; bool samestep = false; for (int i = 0; i < pushoutFacts.Count; i++) { List<Fact> new_list = Fact.MMTFactory(pushoutFacts[i].MapURIs(old_to_new)); if (new_list.Count == 0) { Debug.LogWarning("Parsing on pushout-fact returned empty List -> One of the dependent facts does not exist or parsing failed"); continue; } foreach (Fact new_fact in new_list) { Fact added = FactAdder.AddFactIfNotFound(new_fact, out bool exists, samestep, null, ActiveScroll.label); if (!exists) { new_facts.Add(added); AnimateExistingFactEvent.Invoke(added.Id, FactWrapper.FactMaterials.Solution); samestep = true; } else { // AnimateExistingFactEvent.Invoke(_new, FactWrapper.FactMaterials.Hint); // Automaticly done in FactRecorder old_to_new.Add(new_fact.Id, added.Id); } } //yield return null; } Debug.Log($"Facts parsed within {(System.DateTime.UtcNow - parseTime).TotalMilliseconds}ms"); yield break; } } public void NewAssignmentTrigger() { //return; //if you read this, delete this line! if (ActiveScroll?.ScrollReference == null || NoDynamicScroll.Contains(ActiveScroll.ScrollReference)) return; if (AutomaticHintGenerationActive || DynamicScrollDescriptionsActive) StartCoroutine(_NewAssignment()); IEnumerator _NewAssignment() { if (DynamicScrollInQue) yield break; // only need next in que to finish DynamicScrollInQue = true; while (!SendingViewDone) yield return null; // if we dont wait => server will crash DynamicScrollInQue = false; yield return SendView("/scroll/dynamic"); if (currentMmtAnswer == null) { Debug.LogError("Dynamic Scroll FAILED"); } else { ScrollDynamicInfo scrollDynamicInfo; try { scrollDynamicInfo = JsonConvert.DeserializeObject<ScrollDynamicInfo>(currentMmtAnswer); //scrollDynamicInfo = IJSONsavable<ScrollDynamicInfo>.postprocess(scrollDynamicInfo); // DON'T! will remove hints } catch (JsonSerializationException ex) { Debug.LogException(ex); Debug.LogError("Could not Deserialize MMT aswer for /scroll/dynamic\n" + currentMmtAnswer); yield break; } ScrollApplicationCheckingError[] errors = scrollDynamicInfo.errors .Where(err => err.kind != "nonTotal") // expected .ToArray(); if (errors.Length > 0) ScrollApplicationCheckingErrorEvent.Invoke(errors); processScrollDynamicInfo(scrollDynamicInfo); } } } private IEnumerator SendView(string endpoint) { SendingViewDone = false; while (ParameterDisplays == null) // Wait for server yield return null; string body = prepareScrollAssignments(); using UnityWebRequest www = UnityWebRequest.Put(ServerAdress + endpoint, body); www.method = UnityWebRequest.kHttpVerbPOST; www.SetRequestHeader("Content-Type", "application/json"); System.DateTime sendTime = System.DateTime.UtcNow; yield return www.SendWebRequest(); //if (VerboseURI) Debug.LogFormat("Server answerd in : {0}ms" , (System.DateTime.UtcNow - sendTime).TotalMilliseconds); if (www.result == UnityWebRequest.Result.ConnectionError || www.result == UnityWebRequest.Result.ProtocolError) { Debug.LogWarning(www.error); currentMmtAnswer = null; } else { while (!www.downloadHandler.isDone) yield return null; currentMmtAnswer = www.downloadHandler.text .Replace("\"float\":null", "\"float\":0.0"); // cannot convert null to value type } SendingViewDone = true; yield break; string prepareScrollAssignments() { List<ScrollAssignment> assignmentList = new(); for (int i = 0; i < ParameterDisplays.Count; i++) { Fact tempFact = ParameterDisplays[i].Fact; if (tempFact != null) assignmentList.Add(new ScrollAssignment(ActiveScroll.requiredFacts[i].@ref.uri, tempFact.Id)); } return JsonConvert.SerializeObject(new ScrollApplication(ActiveScroll.ScrollReference, assignmentList)); } } private void processScrollDynamicInfo(ScrollDynamicInfo scrollDynamicInfo) { //TODO: more hints available in scrollDynamicInfo.rendered.requiredFacts LatestBackwartsCompletions = scrollDynamicInfo.backward_completions.Count > 0 ? scrollDynamicInfo.backward_completions[0] : new List<ScrollAssignment>(); List<string> hintUris = LatestBackwartsCompletions .Select(completion => completion.fact.uri) .ToList(); //Update Scroll, process data for later hints and update Uri-List for which hints are available _processRenderedScroll(scrollDynamicInfo.rendered, hintUris); if (AutomaticHintGenerationActive) //Show that Hint is available for ScrollParameter HintAvailableEvent.Invoke(hintUris); return; void _processRenderedScroll(Scroll rendered, List<string> hintUris) { if (DynamicScrollDescriptionsActive) { // Update scroll-description Transform scroll = gameObject.transform.GetChild(1).transform; scroll.GetChild(0).GetComponent<TextMeshProUGUI>().text = rendered.description; } LatestRenderedHints = new(); for (int i = 0; i < rendered.requiredFacts.Count; i++) { RenderedScrollFact RenderedScrollFact = ParameterDisplays .Find(RSF => RSF.ScrollFactURI == rendered.requiredFacts[i].@ref.uri); if (RenderedScrollFact == null) { // e.g. FUNCs //if(CommunicationEvents.VerboseScroll) // Debug.Log($"Descrapancy between requiredFacts and displayed facts:" + // $"Could not find display with ref: {rendered.requiredFacts[i].@ref.uri}"); continue; } if (DynamicScrollDescriptionsActive) //Update ScrollParameter label RenderedScrollFact.Scroll = rendered; //If ScrollFact is assigned -> No Hint if (!RenderedScrollFact.IsSet) { List<Fact> HintFactList = Fact.MMTFactory(rendered.requiredFacts[i]); foreach (Fact HintFact in HintFactList) { hintUris.Add(HintFact.Id); // == rendered.requiredFacts[i].@ref.uri LatestRenderedHints.Add(HintFact); } } } return; } } public void animateHint(string scrollParameterUri) { if (FactRecorder.AllFacts.ContainsKey(scrollParameterUri)) AnimateExistingFactEvent.Invoke( scrollParameterUri, FactWrapper.FactMaterials.Hint ); Fact hintFact = LatestRenderedHints.Find(x => x.Id == scrollParameterUri); // "Dictionary" ScrollAssignment suitableCompletion = LatestBackwartsCompletions.Find((ScrollAssignment x) => x.fact.uri == scrollParameterUri); // "Dictionary" if (suitableCompletion != null && suitableCompletion.assignment is OMS assignment) { if (FactRecorder.AllFacts.ContainsKey(assignment.uri)) { AnimateExistingFactEvent.Invoke( assignment.uri, FactWrapper.FactMaterials.Hint ); } } else if (hintFact != null) { if (FactRecorder.FindEquivalent(StageStatic.stage.factState.MyFactSpace, hintFact, out string found_key, out Fact _, out bool _, false)) // existing fact -> Animate that AnimateExistingFactEvent.Invoke( found_key, FactWrapper.FactMaterials.Hint ); else { // Generate new FactRepresentation and animate it AnimateNonExistingFactEvent.Invoke(hintFact); AnimateExistingFactEvent.Invoke( scrollParameterUri, FactWrapper.FactMaterials.Hint ); } } } }