using System.Collections.Generic; using Newtonsoft.Json; using UnityEngine; using System.Linq; using System; using UnityEngine.InputSystem.Utilities; using static CommunicationEvents; //TODO: MMT: move some functionality there //TODO: consequent!= samestep != dependent //TODO: support renamne functionality /// <summary> /// Organizes (insertion/ deletion / etc. operations) and sepperates <see cref="Fact">Fact</see> spaces. /// Keeps track of insertion/ deletion actions for <see cref="undo"/> and <see cref="redo"/>. /// </summary> public class FactOrganizer : IJSONsavable<FactOrganizer>, IDisposable { /// <summary> /// Contains Immutable <see cref="Fact.Id"/>s; /// e.g: From a <see cref="SolutionOrganizer"/> which are being exposed to the Player. /// <remarks> Will NOT be recorded in <see cref="Workflow"/>!</remarks> /// </summary> [JsonProperty] protected List<string> ImmutableFacts = new(); /// <summary> /// - <c>Key</c>: <see cref="Gadget"/> Used Gadget /// - <c>Value</c>: <see cref="(int first_occurrence, int last_occurrence)"/> First and Last occurence of gadget in Workflow /// </summary> protected Dictionary<Gadget, (int first_occurrence, int last_occurrence)> GadgetWorkflowDict = new(); /// <summary> /// - <c>Key</c>: <see cref="int"/> First occurence of gadget in Workflow /// - <c>Value</c>: <see cref="Gadget"/> Used Gadget /// </summary> [JsonProperty] protected Dictionary<int, Gadget> WorkflowGadgetDict = new() { { -1, null } }; /// <summary> /// - <c>Key</c>: <see cref="Fact.Id"/> /// - <c>Value</c>: <see cref="Fact"/> /// </summary> [JsonIgnore] protected IReadOnlyDictionary<string, Fact> MyFactSpace { get => GlobalFactDictionary.MyFactSpace(this); } [JsonProperty] private IReadOnlyDictionary<string, Fact> JsonFactSpace = null; //null! /// <summary> /// - <c>Key</c>: <see cref="Fact.Id"/> /// - <c>Value</c>: <see cref="Fact"/> /// </summary> public static IReadOnlyDictionary<string, Fact> AllFacts { get => GlobalFactDictionary.AllFacts; } /// <summary> /// - <c>Key</c>: <see cref="Fact.Id"/> /// - <c>Value</c>: <see cref="meta"/> /// </summary> [JsonProperty] protected Dictionary<string, meta> MetaInf = new(); /// <summary> /// Keeps track of insertion/ deletion/ etc. operations for <see cref="undo"/> and <see cref="redo"/> /// </summary> [JsonProperty] protected List<stepnote> Workflow = new(); /// <summary> /// Notes position in <see cref="Workflow"/> for <see cref="undo"/> and <see cref="redo"/>; the pointed to element is non-acitve /// </summary> [JsonProperty] protected int marker = 0; /// <summary> /// Backlock logic redundant - for convinience. /// Keeps track of number of steps in <see cref="Workflow"/>. /// One step can consist of multiple operations. /// <seealso cref="stepnote"/> /// </summary> [JsonProperty] protected int worksteps = 0; /// <summary> /// Backlock logic redundant - for convinience. /// Keeps track of number of steps in <see cref="Workflow"/>, which are not set active. /// One step can consist of multiple operations. /// <seealso cref="stepnote"/> /// </summary> [JsonProperty] protected int backlog = 0; /// <summary> /// Set to <c>true</c> if recently been resetted. /// </summary> [JsonProperty] protected bool soft_resetted = false; /// <summary> /// If set to <c>true</c>, <see cref="Remove(string, bool)"/> and <see cref="Add(Fact, out bool, bool)"/> will invoke <see cref="CommunicationEvents.RemoveFactEvent"/> and <see cref="CommunicationEvents.AddFactEvent"/> respectively. /// </summary> [JsonProperty] public bool invoke; // TODO? SE: better seperation /// <summary> /// Keeps track of maximum <see cref="Fact.LabelId"/> for <see cref="Fact.generateLabel"/>. /// </summary> [JsonProperty] protected internal int MaxLabelId = 0; /// <summary> /// Stores unused <see cref="Fact.LabelId"/> for <see cref="Fact.generateLabel"/>, wich were freed in <see cref="Fact.freeAutoLabel"/> for later reuse to keep naming space compact. /// </summary> [JsonProperty] protected internal SortedSet<int> UnusedLabelIds = new(); // TODO: put this stuff in Interface /// @{ <summary> /// For <see cref="store(string, List<Directories>, bool, bool)"/> and <see cref="load(ref FactOrganizer, bool, string, List<Directories>, bool, out Dictionary<string, string>)"/> /// </summary> public string name { get; set; } = null; public string path { get; set; } = null; /// @} /// <summary> /// Keeps track of insertion/ deletion/ etc. operations for <see cref="undo"/> and <see cref="redo"/> /// Used Terminology /// ================ /// - steproot: elements where <see cref="samestep"/> == <c>true</c> /// - steptail: elements where <see cref="samestep"/> == <c>false</c> /// <seealso cref="Workflow"/> /// </summary> public struct stepnote { /// <summary> <see cref="Fact.Id"/> </summary> public string Id; /// <summary> /// <c>true</c> if this Fact has been created in the same step as the last one /// steproot[false] (=> steptail[true])* /// </summary> public bool samestep; /// <summary> /// For fast access of beginning and end of steps. /// Reference to position in <see cref="Workflow"/> of: /// - steproot: for all elements in steptail /// - after steptail-end: for steproot /// </summary> public int steplink; /// <summary> distincts creation and deletion </summary> public bool creation; /// <summary> /// keeps track with wich <see cref="Gadget"/> the <see cref="Fact"/> is created /// <c>-1</c> iff its not the case /// </summary> public int gadget_rank; /// <summary> /// keeps track with wich <see cref="Scroll"/> the <see cref="Fact"/> is created /// <c>null</c> iff its not the case /// </summary> public string scroll_label; /// <summary>Loggs <see cref="Fact"/>s used by <see cref="Gadget"/></summary> public string[] GadgetFlow; /// <summary>Loggs time, when <see cref="Fact"/> was manipulated</summary> public double GadgetTime; /// <summary> /// Initiator /// </summary> /// <param name="Id"><see cref="Fact.Id"/></param> /// <param name="samestep">sets <see cref="samestep"/></param> /// <param name="creation">sets <see cref="creation"/></param> /// <param name="that"><see cref="FactOrganizer"/> of which <c>this</c> will be added in its <see cref="FactOrganizer.Workflow"/></param> public stepnote(FactOrganizer that, string Id, bool samestep, bool creation, Gadget gadget, string scroll_label) { this.Id = Id; this.samestep = samestep; this.creation = creation; this.scroll_label = scroll_label; this.GadgetTime = StageStatic.stage_time; stepnote prev = default; if (samestep) { prev = that.Workflow[that.marker - 1]; // steplink = !first_steptail ? previous.steplink : steproot this.steplink = prev.samestep ? prev.steplink : that.marker - 1; } else // steproot sets steplink after itself (end of steptail) this.steplink = that.marker + 1; this.GadgetFlow = new string[0]; this.gadget_rank = -1; if (gadget != null) { bool new_gadget; if (new_gadget = !that.GadgetWorkflowDict.ContainsKey(gadget)) { that.GadgetWorkflowDict.Add(gadget, (that.marker, that.marker)); that.WorkflowGadgetDict.Remove(that.marker); that.WorkflowGadgetDict.Add(that.marker, gadget); } var (gadget_first_occurrence, gadget_last_occurrence) = that.GadgetWorkflowDict[gadget]; this.gadget_rank = gadget_first_occurrence; // check for new USE of gadget bool set_workflow = !samestep || new_gadget || prev.gadget_rank != this.gadget_rank || gadget_last_occurrence >= that.marker; stepnote gadget_prev = set_workflow ? default /*unused then*/ : that.Workflow[gadget_last_occurrence]; if (set_workflow || gadget_prev.GadgetFlow == null || !gadget_prev.GadgetFlow.SequenceEqual(gadget.Workflow.ToArray())) { this.GadgetFlow = gadget.Workflow.ToArray(); that.GadgetWorkflowDict[gadget] = (gadget_first_occurrence, that.marker); } } } } /// <summary> /// Each <see cref="Fact"/> entry in <see cref="MyFactSpace"/> has a corresponding <see cref="meta"/> entry in <see cref="MetaInf"/>. /// The <see cref="meta"/> struct is a collection of %meta-variables. /// <seealsocref="PruneWorkflow"/> /// </summary> public struct meta { // TODO? -> public int last_occurence; // for safe_dependencies /// <summary> /// position of first occurrence in <see cref="Workflow"/> /// </summary> public int workflow_id; /// <summary> /// keeps track wether <see cref="Fact"/> is currently in Scene /// </summary> public bool active; /// <summary> /// if <c>true</c> <see cref="Fact"/> is imported from a <see cref="SolutionOrganizer"/> /// </summary> public bool isImmutable; /// <summary> /// Initiator /// </summary> /// <param name="workflow_id">sets <see cref="workflow_id"/></param> /// <param name="active">sets <see cref="active"/></param> /// <param name="isImmutable">sets <see cref="isImmutable"/></param> public meta(int workflow_id, bool active, bool isImmutable) { this.workflow_id = workflow_id; this.active = active; this.isImmutable = isImmutable; } } static FactOrganizer() { IJSONsavable<FactOrganizer>.hierarchie = new List<Directories> { Directories.FactStateMachines }; } /// <summary> /// Only used by <see cref="JsonConverter"/> to initiate empty instance. /// </summary> public FactOrganizer() { GlobalFactDictionary.NewFactSpace(this); this.invoke = false; } ~FactOrganizer() { Dispose(); } public void Dispose() => GlobalFactDictionary.FactSpaceDelete(this); /// <summary> /// Standard Constructor for empty, ready to use <see cref="FactOrganizer"/> /// </summary> /// <param name="invoke">sets <see cref="invoke"/>.</param> public FactOrganizer(bool invoke = false) : this() { this.invoke = invoke; } /// <summary> /// Used to parse read-in <see cref="FactOrganizer"/> by <see cref="JsonReader"/> and make <see cref="Fact.Id"/> conform. /// Also poppulates <see cref="OldToNewURI"/> /// </summary> /// <param name="target">to be parsed into, will be overwritten. /// If <c><paramref name="invoke"/> = true</c>, <paramref name="target"/> should be <see cref="StageStatic.stage.factState"/>, outherwise <see cref="InvokeFactEvent(bool, string)"/> will cause <see cref="Exception">Exceptions</see> when it invokes Events of <see cref="CommunicationEvents"/></param> /// <param name="source">instance to be parsed</param> /// <param name="invoke">see <see cref="invoke"/></param> /// <param name="old_to_new">Generated to map <c>Key</c> outdated <see cref="Fact.Id"/>s to corresponding <c>Value</c> updatated <see cref="Fact.Id"/>s.</param> public static T ReInitializeFactOrganizer<T>(T source, bool invoke, out Dictionary<string, string> old_to_new) where T : FactOrganizer, new() { // TODO: other strategy needed when MMT save/load supported bool source_initialized = GlobalFactDictionary.AllFactSpaces.ContainsKey(source) && source.MyFactSpace.Count > 0; Dictionary<string, string> _old_to_new = source_initialized ? source.MyFactSpace.Keys.ToDictionary(id => id) : new(); // initiate T target = new() { invoke = invoke, MaxLabelId = source.MaxLabelId, UnusedLabelIds = source.UnusedLabelIds, }; // work ExposedSolutionFacts foreach (var element in source.ImmutableFacts) { Fact ExposedFact = ReInitializeFact(element); if (ExposedFact == null) continue; target.Add(ExposedFact, out _, false, null, null, isImmutable: true); } // work Workflow for (int i = 0; i < source.Workflow.Count; i++) { stepnote s_step = source.Workflow[i]; Gadget used_gadget = source.WorkflowGadgetDict[s_step.gadget_rank]; if (used_gadget != null) { Gadget init_gadget = GadgetBehaviour.gadgets?.FirstOrDefault(g => Gadget.Equals(g, used_gadget)); if (init_gadget != null && init_gadget != default(Gadget)) used_gadget = init_gadget; used_gadget.Workflow = s_step.GadgetFlow.Select(uri => _old_to_new[uri]).ToList(); } if (s_step.creation) // Add { Fact add = ReInitializeFact(s_step.Id); target.Add(add, out _, s_step.samestep, used_gadget, s_step.scroll_label); } else if (_old_to_new.TryGetValue(s_step.Id, out string remove_Id)) // Remove { target.Remove(remove_Id, s_step.samestep, used_gadget); } stepnote t_step = target.Workflow[i]; t_step.GadgetTime = s_step.GadgetTime; target.Workflow[i] = t_step; } // set un-redo state while (target.backlog < source.backlog) target.undo(); target.soft_resetted = source.soft_resetted; if (!source_initialized) source.Dispose(); old_to_new = _old_to_new; return target; Fact ReInitializeFact(string old_id) { if (source_initialized) return source.MyFactSpace[old_id]; // is in GlobalFactDictionary.Facts if (_old_to_new.TryGetValue(old_id, out string newId)) return target[newId]; Fact old_Fact = source.JsonFactSpace[old_id]; if (!old_Fact.DependentFactIds.All(id => _old_to_new.ContainsKey(id))) { Debug.LogWarningFormat("Could not Instantiate Immutable Fact: {0}\n\tTrying to compensate...", old_Fact); _old_to_new.Add(old_Fact.Id, old_Fact.Id); return null; } Fact new_Fact = old_Fact.ReInitializeMe(_old_to_new, target); _old_to_new.Add(old_Fact.Id, new_Fact.Id); return new_Fact; } } /// <summary> /// wrappes <c><see cref="MyFactSpace"/>[<paramref name="id"/>]</c> /// <seealso cref="ContainsKey(string)"/> /// </summary> /// <param name="id">a <see cref="Fact.Id"/> in <see cref="MyFactSpace"/></param> /// <returns><c><see cref="MyFactSpace"/>[<paramref name="id"/>]</c></returns> public Fact this[string id] { get => MyFactSpace[id]; } /// <summary> /// Exposes <see cref="Dictionary{TKey, TValue}.TryGetValue(TKey, out TValue)"/> of <see cref="MyFactSpace"/> /// </summary> /// <param name="URI"><see cref="Fact.Id"/> to search for</param> /// <param name="found"><see cref="Fact"/> iff found, else <c>null</c></param> /// <returns>wehter <see cref="Fact"/> with <see cref="Fact.Id"/> was found</returns> public bool TryGetFact(string URI, out Fact found) => MyFactSpace.TryGetValue(URI, out found); /// <summary> /// Exposes contens of <see cref="MetaInf"/> /// <seealso cref="ContainsKey(string)"/> /// </summary> /// <param name="id"> or URI of <see cref="Fact"/></param> /// <returns><see cref="meta"/> <c>struct</c> of given <see cref="Fact"/></returns> public meta GetFactMeta(string id) => MetaInf[id]; /// <summary> /// Exposes contens of <see cref="Workflow"/> /// </summary> /// <param name="index"> to get</param> /// <returns><see cref="stepnote"/> of given <paramref name="index"/></returns> public stepnote GetWorkflow(int index) => Workflow[index]; /// <summary> /// wrappes <c><see cref="MyFactSpace"/>.ContainsKey(<paramref name="id"/>)</c> /// </summary> /// <param name="id">a <see cref="Fact.Id"/></param> /// <returns><c><see cref="MyFactSpace"/>.ContainsKey(<paramref name="id"/>)</c></returns> public bool ContainsKey(string id) => MyFactSpace.ContainsKey(id); /// <summary> /// Looks up if there is a <paramref name="label"/> <see cref="Fact.Label"/> in <see cref="MyFactSpace"/>.Values /// </summary> /// <param name="label">supposed <see cref="Fact.Label"/> to be checked</param> /// <returns><c>true</c> iff <see cref="MyFactSpace"/> conatains a <c>Value</c> <see cref="Fact"/>, where <see cref="Fact.Label"/> == <paramref name="label"/>.</returns> public bool ContainsLabel(string label) { if (string.IsNullOrEmpty(label)) return false; var hit = MyFactSpace.FirstOrDefault(e => e.Value.Label == label); return !hit.Equals(default); } //TODO? MMT? PERF: O(n), every Fact-insertion /// <summary> /// Looks for existent <see cref="Fact"/> (<paramref name="found"/>) which is very similar or identical (<paramref name="exact"/>) to prposed <see cref="Fact"/> (<paramref name="search"/>) /// <remarks>does not check active state</remarks> /// </summary> /// <param name="search">to be searched for</param> /// <param name="found"><see cref="Fact.Id"/> if return value is <c>true</c></param> /// <param name="exact"><c>true</c> iff <paramref name="found"/> == <paramref name="search"/><see cref="Fact.Id">.Id</see></param> /// <returns><c>true</c> iff the exact same or an equivalent <see cref="Fact"/> to <paramref name="search"/> was found in <see cref="MyFactSpace"/></returns> private bool FindEquivalent(Fact search, out string found, out bool exact) { if (exact = MyFactSpace.ContainsKey(search.Id)) { found = search.Id; return true; } foreach (var entry in MyFactSpace) { if (entry.Value.Equivalent(search)) { found = entry.Key; return true; } } found = null; return false; } /// <summary> /// <see cref="PruneWorkflow">prunes</see> & adds <paramref name="note"/> to <see cref="Workflow"/>; <see cref="InvokeFactEvent(bool, string)">Invokes Events</see> /// </summary> /// <param name="note">to be added</param> private void WorkflowAdd(stepnote note) { PruneWorkflow(note); if (note.samestep) // update steplink of steproot { stepnote tmp = Workflow[note.steplink]; tmp.steplink = Workflow.Count + 1; Workflow[note.steplink] = tmp; } else worksteps++; Workflow.Add(note); marker = Workflow.Count; InvokeFactEvent(note.creation, note.Id); } /// <summary> /// set current (displayed) state in stone, a.k.a. <see cref="Fact.delete(bool)">delete</see> non <see cref="meta.active"/> <see cref="Fact">Facts</see> for good; /// resets <see cref="undo">un</see>-<see cref="redo"/> parameters /// </summary> private void PruneWorkflow(stepnote not_me) { /*if (soft_resetted) this.hardreset(false); // musn't clear else*/ if (backlog > 0) { worksteps -= backlog; backlog = 0; for (int i = Workflow.Count - 1; i >= marker; i--) // cleanup now obsolete Facts { stepnote last = Workflow[i]; if (last.gadget_rank == MetaInf[last.Id].workflow_id && last.gadget_rank != not_me.gadget_rank) { // Remove Gadget, if its the first time it's beeing used GadgetWorkflowDict.Remove(WorkflowGadgetDict[last.gadget_rank]); WorkflowGadgetDict.Remove(last.gadget_rank); } if (last.Id != not_me.Id // may be zombie && MetaInf[last.Id].workflow_id == i) // remove for good, if original creation gets pruned { GlobalFactDictionary.FactSpaceRemove(this, last.Id); MetaInf.Remove(last.Id); } } // prune Worklfow down to marker Workflow.RemoveRange(marker, Workflow.Count - marker); } } /// <summary> /// Call this to Add a <see cref="Fact"/> to <see cref="FactOrganizer">this</see> instance. /// <remarks>*Warning*: If return_value != <paramref name="value"/><see cref="Fact.Id">.Id</see>, <paramref name="value"/> will be <see cref="Fact.delete(bool)">deleted</see> for good to reduce ressource usage!</remarks> /// </summary> /// <param name="value">to be added</param> /// <param name="exists"><c>true</c> iff <paramref name="value"/> already exists (may be <see cref="meta.active">inactive</see> before opreation)</param> /// <param name="samestep">set <c>true</c> if <see cref="Fact"/> creation happens as a subsequent/ consequent step of multiple <see cref="Fact"/> creations and/or deletions, /// and you whish that these are affected by a single <see cref="undo"/>/ <see cref="redo"/> step</param> /// <param name="gadget">the <see cref="Gadget"/>used to create this <see cref="Fact"/> or <c>null</c></param> /// <param name="scroll_label">the <see cref="Scroll"/>used to create this <see cref="Fact"/> or <c>null</c></param> /// <param name="isImmutable">will eneable delete protection, will BE recorded in <see cref="ImmutableFacts"/>, will NOT be recorded in <see cref="Workflow"/></param> /// <returns><see cref="Fact.Id"/> of <paramref name="value"/> or <see cref="FindEquivalent(Fact, out string, out bool)">found</see> <see cref="Fact"/> iff <paramref name="exists"/>==<c>true</c></returns> public string Add(Fact value, out bool exists, bool samestep, Gadget gadget, string scroll_label, bool isImmutable = false) { soft_resetted = false; #pragma warning disable IDE0018 // Inlinevariablendeklaration string key; #pragma warning restore IDE0018 // Inlinevariablendeklaration if (exists = FindEquivalent(value, out key, out bool _)) { if (exists = MetaInf[key].active) //Fact in Scene? // desired outcome already achieved return key; if (MetaInf[key].workflow_id > marker) // update meta data: everything >= marker will be pruned (except this Fact) MetaInf[key] = new meta(marker, true, isImmutable); } else // brand new Fact { key = value.Id; GlobalFactDictionary.FactSpaceAdd(this, value); MetaInf.Add(key, new meta(marker, true, isImmutable)); } if (isImmutable) { ImmutableFacts.Add(key); InvokeFactEvent(true, key); } else WorkflowAdd(new stepnote(this, key, samestep, true, gadget, scroll_label)); return key; } /// <summary> /// Call this to Remove a <see cref="Fact"/> from <see cref="FactOrganizer">this</see> instance. /// If other <see cref="Fact">Facts</see> depend on <paramref name="value"/> <see cref="Remove(Fact, bool)">Remove(/<depending Fact/>, <c>true</c>)</see> will be called recursively/ cascadingly. /// </summary> /// <remarks>this will not <see cref="Fact.delete(bool)">delete</see> a <see cref="Fact"/>, but sets it <see cref="meta.active">inactive</see> for later <see cref="Fact.delete(bool)">deletion</see> when <see cref="PruneWorkflow">pruned</see>.</remarks> /// <param name="value">to be removed</param> /// <param name="samestep">set <c>true</c> if <see cref="Fact"/> deletion happens as a subsequent/ consequent step of multiple <see cref="Fact"/> creations and/or deletions, /// and you whish that these are affected by a single <see cref="undo"/>/ <see cref="redo"/> step</param> /// <param name="gadget">the <see cref="Gadget"/>used to delete <paramref name="value"/> or <c>null</c></param> /// <param name="deleteSolutionFact">can overwrite protection from <see cref="Fact"/>s imported from a <see cref="SolutionOrganizer"/></param> /// <returns><c>true</c> iff <paramref name="value"/><see cref="Fact.Id">.Id</see> was found.</returns> public bool Remove(Fact value, bool samestep, Gadget gadget, bool deleteSolutionFact = false) => this.Remove(value.Id, samestep, gadget, deleteSolutionFact); /// \copybrief Remove(Fact, bool) /// <remarks>this will not <see cref="Fact.delete(bool)">delete</see> a <see cref="Fact"/>, but sets it <see cref="meta.active">inactive</see> for later <see cref="Fact.delete(bool)">deletion</see> when <see cref="PruneWorkflow">pruned</see>.</remarks> /// <param name="key">to be removed</param> /// <param name="samestep">set <c>true</c> if <see cref="Fact"/> deletion happens as a subsequent/ consequent step of multiple <see cref="Fact"/> creations and/or deletions, /// and you whish that these are affected by a single <see cref="undo"/>/ <see cref="redo"/> step</param> /// <param name="gadget">the <see cref="Gadget"/>used to delete <paramref name="value"/> or <c>null</c></param> /// <param name="deleteImmutables">can overwrite protection from <see cref="Fact"/>s in <see cref="ImmutableFacts"/></param> /// <returns><c>true</c> iff <paramref name="value"/> was found.</returns> public bool Remove(string key, bool samestep, Gadget gadget, bool deleteImmutables = false) //no reset check needed (impossible state) { if (!MyFactSpace.ContainsKey(key)) return false; if (!deleteImmutables && MetaInf[key].isImmutable) // may not be deleted return false; if (!MetaInf[key].active) // desiered outcome reality return true; //TODO: see issue #58 safe_dependencies(key, out List<string> deletethis); if (deletethis.Count > 0) { yeetusdeletus(deletethis, samestep, gadget); } return true; } // TODO: MMT: decide dependencies there (remember virtual deletions in Unity (un-redo)!) // TODO? decrease runtime from amorised? O((n/2)^2) /// <summary> /// searches recursively for <see cref="Fact">Facts</see> where <see cref="Fact.getDependentFactIds"/> includes <paramref name="key"/>/ found dependencies /// </summary> /// <param name="key">to be cross referenced</param> /// <param name="dependencies">all <see cref="Fact">Facts</see> where <see cref="Fact.getDependentFactIds"/> includes <paramref name="key"/>/ found dependencies</param> /// <returns><c>false</c> if any dependencies are <see cref="stepnote">steproots</see></returns> public bool safe_dependencies(string key, out List<string> dependencies) { dependencies = new List<string>(); int c_unsafe = 0; int pos = MetaInf[key].workflow_id; dependencies.Add(key); // accumulate facts that are dependent of dependencies for (int i = pos; i < marker; i++) { // TODO: consequent != samestep != dependent (want !consequent) if (!Workflow[i].creation && Workflow[i].Id != key) { // just try if (dependencies.Remove(Workflow[i].Id) && !Workflow[i].samestep) c_unsafe--; } else if (this[Workflow[i].Id].DependentFactIds.Intersect(dependencies).Any() && Workflow[i].Id != key) { dependencies.Add(Workflow[i].Id); if (!Workflow[i].samestep) c_unsafe++; } } return c_unsafe == 0; } /// <summary> /// Turns every <see cref="Fact"/> in <paramref name="deletereverse"/> (in reverse order) <see cref="meta.active">inactive</see>, as it would be <see cref="Remove(string, bool)">removed</see>, but without checking for (recursive) dependencies. /// </summary> /// <param name="deletereverse">to be <see cref="Remove(string, bool)">removed</see>, but without checking for (recursive) dependencies</param> /// <param name="samestep">see <see cref="Remove(string, bool).samestep"/>. Only applies to last (first iteration) element of <paramref name="deletereverse"/>; for everything else <paramref name="samestep"/> will be set to <c>true</c>.</param> private void yeetusdeletus(List<string> deletereverse, bool samestep, Gadget gadget) { for (int i = deletereverse.Count - 1; i >= 0; i--, samestep = true) { WorkflowAdd(new stepnote(this, deletereverse[i], samestep, false, gadget, null)); } } /// <summary> /// reverses any entire step; adds process to Workflow! /// </summary> /// <remarks>*Warning*: unused therefore untested and unmaintained.</remarks> /// <param name="pos">position after <see cref="stepnote">steptail-end</see> of the step to be reversed</param> /// <param name="samestep">see <see cref="yeetusdeletus(List<string>, bool).samestep"/></param> private void reversestep(int pos, bool samestep = false) { pos--; if (pos >= marker) // check for valid step (implicit reset check) return; for (int i = pos, stop = Workflow[pos].samestep ? Workflow[pos].steplink : pos; i >= stop; i--, samestep = true) { if (Workflow[i].creation) Remove(Workflow[i].Id, samestep, null); else if (!MetaInf[Workflow[i].Id].active) WorkflowAdd(new stepnote(this, Workflow[i].Id, samestep, true, null, null)); } } /// <summary> /// Undoes an entire <see cref="stepnote">step</see> or last <see cref="softreset"/> . /// No <see cref="Fact"/> will be actually <see cref="Add(Fact, out bool, bool)">added</see>, <see cref="Remove(Fact, bool)">removed</see> or <see cref="Fact.delete(bool)">deleted</see>; only its visablity and <see cref="meta.active"/> changes. /// <seealso cref="marker"/> /// <seealso cref="worksteps"/> /// <seealso cref="backlog"/> /// </summary> public void undo() { if (soft_resetted) fastforward(); // revert softreset else if (backlog < worksteps) { backlog++; stepnote last = Workflow[--marker]; int stop = last.samestep ? last.steplink : marker; for (int i = marker; i >= stop; i--) { last = Workflow[i]; InvokeFactEvent(!last.creation, last.Id); } marker = stop; } } /// <summary> /// Redoes an entire <see cref="stepnote">step</see> . /// No <see cref="Fact"/> will be actually <see cref="Add(Fact, out bool, bool)">added</see>, <see cref="Remove(Fact, bool)">removed</see> or <see cref="Fact.delete(bool)">deleted</see>; only its visablity and <see cref="meta.active"/> changes. /// <seealso cref="marker"/> /// <seealso cref="worksteps"/> /// <seealso cref="backlog"/> /// </summary> public void redo() { soft_resetted = false; if (backlog > 0) { backlog--; stepnote last = Workflow[marker]; int stop = last.samestep ? Workflow[last.steplink].steplink : last.steplink; for (int i = marker; i < stop; i++) { last = Workflow[i]; InvokeFactEvent(last.creation, last.Id); } marker = stop; } } /// <summary> /// Resets to "factory conditions". /// Neither <see cref="Fact.delete(bool)">deletes</see> <see cref="Fact">Facts</see> nor invokes <see cref="CommunicationEvents.RemoveFactEvent"/> /// </summary> /// <seealso cref="hardreset(bool)"/> public void Clear() { GlobalFactDictionary.FactSpaceClear(this); MetaInf.Clear(); Workflow.Clear(); ImmutableFacts.Clear(); GadgetWorkflowDict.Clear(); marker = 0; worksteps = 0; backlog = 0; soft_resetted = false; } /// <summary> /// Resets to "factory conditions". /// <see cref="Fact.delete(bool)">deletes</see> <see cref="Fact">Facts</see> and invokes <see cref="CommunicationEvents.RemoveFactEvent"/> iff <paramref name="invoke_event"/> && <see cref="invoke"/>. /// </summary> /// <seealso cref="Clear"/> /// <param name="invoke_event">if set to <c>true</c> *and* <see cref="invoke"/> set to <c>true</c> will invoke <see cref="CommunicationEvents.RemoveFactEvent"/></param> public void hardreset(bool invoke_event = true) { if (invoke_event && invoke) foreach (var entry in MyFactSpace) if (MetaInf[entry.Key].active) CommunicationEvents.RemoveFactEvent.Invoke(entry.Value); this.Clear(); } /// <summary> /// <see cref="undo">Undoes</see> *all* <see cref="worksteps"/> (since <see cref="marker"/>) and sets <see cref="soft_resetted"/> to <c>true</c>. /// </summary> public void softreset() { if (soft_resetted) { fastforward(); return; } while (marker > 0) undo(); // marker = 0; backlog = worksteps; soft_resetted = true; } /// <summary> /// <see cref="redo">Redoes</see> *all* <see cref="worksteps"/> (from <see cref="marker"/> onwards) and sets <see cref="soft_resetted"/> to <c>false</c>. /// </summary> public void fastforward() { while (backlog > 0) // also sets resetted = false; redo(); } FactOrganizer IJSONsavable<FactOrganizer>._IJPostProcess(FactOrganizer raw_payload) => raw_payload == null ? raw_payload : ReInitializeFactOrganizer<FactOrganizer>(raw_payload, false, out _); FactOrganizer IJSONsavable<FactOrganizer>._IJPreProcess(FactOrganizer payload) { if (GlobalFactDictionary.AllFactSpaces.ContainsKey(payload) && payload.MyFactSpace.Count > 0) payload.JsonFactSpace = payload.MyFactSpace; return payload; } /// <summary> /// Call this after assigning a stored instance in an empty world, that was not drawn. /// <see cref="redo">Redoes</see>/ draws everything from <see cref="marker"/> = 0 to <paramref name="draw_all"/><c> ? worksteps : backlog</c> /// </summary> /// <remarks>Does not invoke <see cref="softreset"/> or <see cref="undo"/> in any way and thus may trigger <see cref="Exception">Exceptions</see> or undefined behaviour if any <see cref="Fact"/> in <see cref="MyFactSpace"/> is already drawn.</remarks> public void Draw(bool draw_all = false) { // TODO: see issue #58 // TODO: communication with MMT foreach (var key in ImmutableFacts) InvokeFactEvent(true, key); // sets info.active = true marker = 0; var stop = draw_all ? worksteps : backlog; backlog = worksteps; while (backlog > stop) redo(); // resets info.active } /// <summary> /// Undraws everything by invoking <see cref="CommunicationEvents.RemoveFactEvent"/>, that is <see cref="meta.active"/>, but does not change that satus. /// </summary> /// <param name="force_invoke">if set <c>true</c>, invokes <see cref="CommunicationEvents.RemoveFactEvent"/> for every <see cref="Fact"/> regardles of <see cref="meta.active"/> status or <see cref="invoke"/></param> public void Undraw(bool force_invoke = false) { foreach (var entry in MyFactSpace) { if (force_invoke || (invoke && MetaInf[entry.Key].active)) CommunicationEvents.RemoveFactEvent.Invoke(entry.Value); } } /// <summary> /// Updates <see cref="MetaInf"/>, <see cref="Fact.Label"/> and invokes <see cref="CommunicationEvents"/> (latter iff <see cref="invoke"/> is set) /// </summary> /// <param name="creation">wether <see cref="Fact"/> is created or removed</param> /// <param name="Id"><see cref="Fact.Id"/></param> private void InvokeFactEvent(bool creation, string Id) { // update meta struct meta info = MetaInf[Id]; info.active = creation; MetaInf[Id] = info; if (invoke) if (creation) CommunicationEvents.AddFactEvent.Invoke(this[Id]); else CommunicationEvents.RemoveFactEvent.Invoke(this[Id]); if (creation) // undo freeLabel() _ = MyFactSpace[Id].Label; else MyFactSpace[Id].freeAutoLabel(); } /// <summary> /// Used to check wether <see cref="FactOrganizer">this</see> satisfies the constrains of an <see cref="SolutionOrganizer">Solution</see>. /// Only <see cref="meta.active"/> are accounted for. /// </summary> /// <param name="MinimalSolution">describes constrains</param> /// <param name="MissingElements">elements which were *not* found in <see cref="SolutionOrganizer.ValidationSet"/> in a format reflecting that of <see cref="SolutionOrganizer.ValidationSet"/></param> /// <param name="Solutions">elements which *were* found in <see cref="SolutionOrganizer.ValidationSet"/> in a format reflecting that of <see cref="SolutionOrganizer.ValidationSet"/></param> /// <returns><c>true</c> iff *all* constrains set by <paramref name="MinimalSolution"/> are met</returns> public bool DynamiclySolved( SolutionOrganizer MinimalSolution, out List<List<string>> MissingElements, out List<List<string>> Solutions) { MissingElements = new List<List<string>>(); // need to work not on ref/out List<List<string>> Solution_L = new(); int MissingElementsCount = 0; var activeList = MyFactSpace.Values.Where(f => MetaInf[f.Id].active); foreach (var ValidationSet in MinimalSolution.ValidationSet) { // List to relato to. Either all active facts or those defined in RelationIndex if not empty var relateList = ValidationSet.RelationIndex.Count == 0 ? activeList : ValidationSet.RelationIndex.Select(i => Solution_L[i]) // Select by Index .SelectMany(i => i) // Flatten structure .Select(URI => this[URI]); // Get Facts // check by MasterIds // ALL Masters must relate var part_minimal = ValidationSet.MasterIDs.Select(URI => MinimalSolution[URI]); var part_solution = relateList.Where(active => part_minimal.Contains(active, ValidationSet.Comparer.SetSearchRight())) .ToList(); // needed for some reason var part_missing = part_minimal.Except(part_solution, ValidationSet.Comparer.SetSearchLeft()); // SolutionIndex may include current index Solution_L.Add(part_solution.Select(fact => fact.Id).ToList()); MissingElements.Add(part_missing.Select(fact => fact.Id).ToList()); MissingElementsCount += part_missing.Count(); // check by previous solutions // at least ONE member must relate var part_consequential_minimal = ValidationSet.SolutionIndex.Select(i => Solution_L[i]) // Select by Index .SelectMany(i => i) // Flatten structure .Select(URI => this[URI]); // Get Facts var part_consequential_solution = relateList.Where(active => part_consequential_minimal.Contains(active, ValidationSet.Comparer.SetSearchRight())); Solution_L.Last().AddRange(part_consequential_solution.Select(fact => fact.Id).ToList()); MissingElementsCount += Convert.ToInt32( part_consequential_solution.Count() == 0 && part_consequential_minimal.Count() != 0); } Solutions = Solution_L; return MissingElementsCount == 0; } public IEnumerable<Gadget> GetUsedGadgets() => GadgetWorkflowDict.Keys; public int GetNumberOfGadgets() => GadgetWorkflowDict.Count; public IEnumerable<string> GetUsedScrolls() => Workflow.Where(sn => MetaInf[sn.Id].active && sn.scroll_label != null).Select(sn => sn.scroll_label).Distinct(); public int GetNumberOfScrolls() => GetUsedScrolls().Count(); public int GetNumberOfFacts() => MyFactSpace.Count; protected static class GlobalFactDictionary { /// <summary> /// - <c>Key</c>: <see cref="Fact.Id"/> /// - <c>Value</c>: <see cref="Fact"/> /// </summary> private static readonly Dictionary<string, Fact> FactDict = new(); public static IReadOnlyDictionary<string, Fact> AllFacts { get => FactDict; } private static readonly Dictionary<string, uint> FactReferences = new(); private static readonly Dictionary<FactOrganizer, Dictionary<string, Fact>> FactSpaces = new(); public static IReadOnlyDictionary<FactOrganizer, Dictionary<string, Fact>> AllFactSpaces => FactSpaces; public static IReadOnlyDictionary<string, Fact> MyFactSpace(FactOrganizer me) => FactSpaces[me]; public static void NewFactSpace(FactOrganizer me) { FactSpaces.Add(me, new()); } public static void FactSpaceClear(FactOrganizer me) { if (!FactSpaces.ContainsKey(me)) return; foreach (string key in FactSpaces[me].Keys.ToArray()) FactSpaceRemove(me, key); } public static void FactSpaceDelete(FactOrganizer me) { FactSpaceClear(me); FactSpaces.Remove(me); } public static void FactSpaceAdd(FactOrganizer me, Fact fact) { if (!FactDict.ContainsKey(fact.Id)) { FactDict.Add(fact.Id, fact); FactReferences.Add(fact.Id, 1); } else FactReferences[fact.Id]++; if (!FactSpaces.ContainsKey(me)) FactSpaces.Add(me, new()); FactSpaces[me].Add(fact.Id, fact); } public static void FactSpaceRemove(FactOrganizer me, string key) { FactSpaces[me].Remove(key); if (FactReferences[key] == 1) { FactDict.Remove(key); FactReferences.Remove(key); } else FactReferences[key]--; } } }