using System.Collections.Generic; using System.Reflection; using System.IO; using Newtonsoft.Json; using System.Collections; using UnityEngine; using System.Linq; using System; //TODO: MMT: move some functionality there //TODO: consequent!= samestep != dependent //PERF: avoid string as key (general: allocations & dict: hash -> colission? -> strcmp[!]) public class FactOrganizer { protected internal Dictionary<string, Fact> FactDict; protected internal Dictionary<string, meta> MetaInf = new Dictionary<string, meta>(); protected internal List<stepnote> Workflow = new List<stepnote>(); // notes position in Workflow for un-/redo; the pointed to element is non-acitve protected internal int marker = 0; // backlock logic for convinience protected internal int worksteps = 0; protected internal int backlog = 0; // set if recently been resetted protected internal bool soft_resetted = false; // InvokeEvents? public bool invoke; protected internal struct stepnote { // Fact.Id public string Id; // true if this Fact has been created in the same step as the last one // steproot[false] (=> steptail[true])* public bool samestep; // reference to steproot/ after steptail-end public int steplink; // distincts creation and deletion public bool creation; public stepnote(string Id, bool samestep, bool creation, FactOrganizer that) { this.Id = Id; this.samestep = samestep; this.creation = creation; if (samestep) // steplink = !first_steptail ? previous.steplink : steproot { stepnote prev = that.Workflow[that.marker - 1]; this.steplink = prev.samestep ? prev.steplink : that.marker - 1; } else // steproot sets steplink after itself (end of steptail) this.steplink = that.marker + 1; } } protected internal struct meta { // TODO? -> public int last_occurence for safe_dependencies // reference to first occurrence in Workflow public int workflow_id; // keeps track wether Fact is currently in Scene public bool active; public meta(int workflow_id, bool active = true) { this.workflow_id = workflow_id; this.active = active; } } public FactOrganizer(bool invoke = false) { FactDict = new Dictionary<string, Fact>(); this.invoke = invoke; } private FactOrganizer(ref FactOrganizer set, PublicFactOrganizer exposed, bool invoke) { // TODO: other strategy needed when MMT save/load supported // map old URIs to new ones Dictionary<string, string> old_to_new = new Dictionary<string, string>(); // combine T:Fact to Fact Dictionary<string, Fact> old_FactDict = new Dictionary<string, Fact>(); /* FieldInfo[] finfos = typeof(PublicFactOrganizer).GetFields(); foreach(string type in PublicFactOrganizer.WatchedFacts) AddListToDict( finfos.First(x => x.Name.Remove(x.Name.Length-1) == type) .GetValue(exposed) as List<Fact>); */ AddListToDict(exposed.PointFacts); AddListToDict(exposed.LineFacts); AddListToDict(exposed.RayFacts); AddListToDict(exposed.AngleFacts); AddListToDict(exposed.OnLineFacts); // initiate this.invoke = invoke; FactDict = new Dictionary<string, Fact>(); set = this; // work Workflow foreach (var sn in exposed.Workflow) { if (sn.creation) // Add { Fact add; if (old_to_new.ContainsKey(sn.Id)) add = FactDict[old_to_new[sn.Id]]; else { Fact old_Fact = old_FactDict[sn.Id]; // TODO! false customLabel add = old_Fact.GetType() .GetConstructor(new Type[] { old_Fact.GetType(), old_to_new.GetType(), typeof(FactOrganizer) }) .Invoke(new object[] { old_Fact, old_to_new, this }) as Fact; old_to_new.Add(sn.Id, add.Id); } Add(add, out _, sn.samestep); } else if(old_to_new.ContainsKey(sn.Id)) // Remove { Fact remove = FactDict[old_to_new[sn.Id]]; Remove(remove, sn.samestep); } } // set un-redo state while (this.backlog < exposed.backlog) undo(); this.soft_resetted = exposed.soft_resetted; // === local functions === void AddListToDict<T>(List<T> list) where T:Fact { foreach (T ft in list) old_FactDict.Add(ft.Id, ft); } } public Fact this[string id] { get { return FactDict[id]; } } public bool ContainsKey(string id) { return FactDict.ContainsKey(id); } public bool ContainsLabel(string label) { if (string.IsNullOrEmpty(label)) return false; var hit = FactDict.FirstOrDefault(e => e.Value.Label == label); return !hit.Equals(System.Activator.CreateInstance(hit.GetType())); } //TODO? MMT? PERF: O(n), every Fact-insertion private bool FindEquivalent(Fact search, out string found, out bool exact) // Looks for existent facts (found) which are very similar to prposed fact (search) // does not check active state { if (exact = FactDict.ContainsKey(search.Id)) { found = search.Id; return true; } foreach (var entry in FactDict) { if (entry.Value.Equivalent(search)) { found = entry.Key; return true; } } found = null; return false; } private void WorkflowAdd(stepnote note) // prunes & adds Workflow; Invokes Events { PruneWorkflow(); 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); } private void PruneWorkflow() // set current (displayed) state in stone; resets un-redo parameters { /*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.creation // may be zombie && MetaInf[last.Id].workflow_id == i) // remove for good, if original creation gets pruned { this[last.Id].delete(); FactDict.Remove(last.Id); MetaInf.Remove(last.Id); } } // prune Worklfow down to marker Workflow.RemoveRange(marker, Workflow.Count - marker); } } public string Add(Fact value, out bool exists, bool samestep = false) // also checks for duplicates and active state // returns key of actual Fact { string key; if (exists = FindEquivalent(value, out key, out bool exact)) { if (!exact) // no longer needed value.delete(); if (MetaInf[key].workflow_id >= marker) // check for zombie-status { // protect zombie from beeing pruned var zombie = Workflow[MetaInf[key].workflow_id]; zombie.creation = false; Workflow[MetaInf[key].workflow_id] = zombie; // set new init location MetaInf[key] = new meta(marker, true); } // zombies are undead! else if (MetaInf[key].active) // desired outcome already achieved return key; } else // brand new Fact { key = value.Id; FactDict.Add(key, value); MetaInf.Add(key, new meta(marker, true)); } WorkflowAdd(new stepnote(key, samestep, true, this)); return key; } public bool Remove(Fact value, bool samestep = false) { return this.Remove(value.Id, samestep); } public bool Remove(string key, bool samestep = false) //no reset check needed (impossible state) { if (!FactDict.ContainsKey(key)) return false; //TODO: see issue #58 safe_dependencies(key, out List<string> deletethis); if(deletethis.Count > 0) { yeetusdeletus(deletethis, samestep); } return true; } // TODO: MMT: decide dependencies there (remember virtual deletions in Unity (un-redo)!) // TODO? decrease runtime from O(n/2) public bool safe_dependencies(string key, out List<string> dependencies) // searches for dependencies of a Fact; returns false if any dependencies are steproots // int key: Fact to be deleted // out List<int> dependencies: dependencyList { 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) { // just try if (dependencies.Remove(Workflow[i].Id) && !Workflow[i].samestep) c_unsafe--; } else if (0 < this[Workflow[i].Id].getDependentFactIds().Intersect(dependencies).Count()) { dependencies.Add(Workflow[i].Id); if (!Workflow[i].samestep) c_unsafe++; } } return c_unsafe == 0; } private void yeetusdeletus(List<string> deletereverse, bool samestep = false) { for(int i = deletereverse.Count - 1; i >= 0; i--, samestep = true) { WorkflowAdd(new stepnote(deletereverse[i], samestep, false, this)); } } private void reversestep(int pos, bool samestep = false) // reverses any entire step; adds process to Workflow! // int pos: position after steptail-end of the step to be reversed { 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); else WorkflowAdd(new stepnote(Workflow[i].Id, samestep, true, this)); } } 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; } } 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; } } public void Clear() // Does not Invoke RemoveFactEvent(s)! { FactDict.Clear(); MetaInf.Clear(); Workflow.Clear(); marker = 0; worksteps = 0; backlog = 0; soft_resetted = false; } public void hardreset(bool invoke_event = true) { foreach(var entry in FactDict) { if (invoke_event && invoke && MetaInf[entry.Key].active) CommunicationEvents.RemoveFactEvent.Invoke(entry.Value); entry.Value.delete(); } this.Clear(); } public void softreset() { if (soft_resetted) { fastforward(); return; } // TODO: PREF: alt: EventResetAll // marker = 0; backlog = worksteps; while (marker > 0) undo(); soft_resetted = true; } public void fastforward() { while (backlog > 0) // also sets resetted = false; redo(); } public void store(string name, bool use_type_subfolder = true) { string path = CommunicationEvents.CreatePathToFile(out _, name, "JSON", use_type_subfolder ? typeof(FactOrganizer) : null); // note: max depth for "this" is 2, since Fact has non-serilazible member, that is not yet ignored (see Fact.[JasonIgnore] and JSONManager.WriteToJsonFile) // using public dummy class to circumvent deserialiation JsonInheritanceProblem (see todos @PublicFactOrganizer) JSONManager.WriteToJsonFile(path, new PublicFactOrganizer(this), 0); } public static void load(ref FactOrganizer set, bool draw, string name, bool use_type_subfolder = true, bool reset_Fact = false) { string path = CommunicationEvents.CreatePathToFile(out _, name, "JSON", use_type_subfolder ? typeof(FactOrganizer) : null); PublicFactOrganizer de_json = JSONManager.ReadFromJsonFile<PublicFactOrganizer>(path); new FactOrganizer(ref set, de_json, draw); if (reset_Fact) Fact.Clear(); } public void load(bool draw_all = true) // call this after assigning a stored instance in an empty world, that was not drawn { // TODO: see issue #58 // TODO: communication with MMT foreach (var mett in MetaInf) { // update active info if needed if (mett.Value.active) { meta info = mett.Value; info.active = false; MetaInf[mett.Key] = info; } } marker = 0; var stop = backlog; backlog = worksteps; if(draw_all) while(backlog > stop) redo(); } 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() _ = FactDict[Id].Label; else FactDict[Id].freeAutoLabel(); } public bool StaticlySovled(List<Fact> StaticSolution, out List<Fact> MissingElements, out List<Fact> Solutions) // QoL for simple Levels { return DynamiclySolved(StaticSolution, out MissingElements, out Solutions, new FactEquivalentsComparer()); } //TODO: PERF: see CommunicationEvents.Solution public bool DynamiclySolved(List<Fact> MinimalSolution, out List<Fact> MissingElements, out List<Fact> Solutions, FactComparer FactComparer) { Solutions = FactDict.Values.Where(f => MetaInf[f.Id].active) .Where(active => MinimalSolution.Contains(active, FactComparer.SetSearchRight())) .ToList(); MissingElements = MinimalSolution.Except(Solutions, FactComparer.SetSearchLeft()).ToList(); return MissingElements.Count == 0; } //TODO: repair public bool DynamiclySolvedEXP( (FactOrganizer solutionorganizer, List<(HashSet<string> subsolution, FactComparer comparer)> validationsets) MinimalSolution, out List<List<string>> MissingElements, out List<List<string>> Solutions) { MissingElements = new List<List<string>>(); Solutions = new List<List<string>>(); int MissingElementsCount = 0; var activeList = FactDict.Values.Where(f => MetaInf[f.Id].active); foreach (var ValidationSet in MinimalSolution.validationsets) { var part_minimal = ValidationSet.subsolution.Select(URI => MinimalSolution.solutionorganizer[URI]); var part_solution = activeList.Where(active => part_minimal.Contains(active, ValidationSet.comparer.SetSearchRight())); var part_missing = part_minimal.Except(part_solution, ValidationSet.comparer.SetSearchLeft()); Solutions.Add(part_solution.Select(fact => fact.Id).ToList()); MissingElements.Add(part_missing.Select(fact => fact.Id).ToList()); MissingElementsCount += part_missing.Count(); } return MissingElementsCount == 0; } } // TODO? PERF? SE? JsonInheritanceProblem: scrap this hardwired class and implement dynamic approach with JsonConverter (see: JSONManager.JsonInheritenceConverter) public class PublicFactOrganizer : FactOrganizer // public class exposing all protected members of FactOrganizer for JSON conversion { // TODO? check once if those are all with reflection protected internal static List<string> WatchedFacts = new List<string>(new string[] { "PointFact", "LineFact", "RayFact", "OnLineFact", "AngleFact" }); public List<PointFact> PointFacts = new List<PointFact>(); public List<LineFact> LineFacts = new List<LineFact>(); public List<RayFact> RayFacts = new List<RayFact>(); public List<OnLineFact> OnLineFacts = new List<OnLineFact>(); public List<AngleFact> AngleFacts = new List<AngleFact>(); public new Dictionary<string, meta> MetaInf = new Dictionary<string, meta>(); public new List<stepnote> Workflow = new List<stepnote>(); // notes position in Workflow for un-/redo; the pointed to element is non-acitve public new int marker = 0; // backlock logic for convinience public new int worksteps = 0; public new int backlog = 0; // set if recently been resetted public new bool soft_resetted = false; // InvokeEvents? public new bool invoke; public new struct stepnote { // Fact.Id public string Id; // true if this Fact has been created in the same step as the last one // steproot[false] (=> steptail[true])* public bool samestep; // reference to steproot/ after steptail-end public int steplink; // distincts creation and deletion public bool creation; public stepnote(string Id, bool samestep, int steplink, bool creation) { this.Id = Id; this.samestep = samestep; this.steplink = steplink; this.creation = creation; } /*public stepnote(string Id, bool samestep, bool creation, PublicFactOrganizer that) { this.Id = Id; this.samestep = samestep; this.creation = creation; if (samestep) // steplink = !first_steptail ? previous.steplink : steproot { stepnote prev = that.Workflow[that.marker - 1]; this.steplink = prev.samestep ? prev.steplink : that.marker - 1; } else // steproot sets steplink after itself (end of steptail) this.steplink = that.marker + 1; }*/ } public new struct meta { // TODO? -> public int last_occurence for safe_dependencies // reference to first occurrence in Workflow public int workflow_id; // keeps track wether Fact is currently in Scene public bool active; public meta(int workflow_id, bool active) { this.workflow_id = workflow_id; this.active = active; } } public PublicFactOrganizer() { FactDict = new Dictionary<string, Fact>(); this.invoke = false; } protected internal PublicFactOrganizer(FactOrganizer expose) { // expose all non-abstract members marker = expose.marker; worksteps = expose.worksteps; backlog = expose.backlog; soft_resetted = expose.soft_resetted; invoke = expose.invoke; foreach (var sn in expose.Workflow) Workflow.Add(new stepnote(sn.Id, sn.samestep, sn.steplink, sn.creation)); foreach (var mt in expose.MetaInf) MetaInf.Add(mt.Key, new meta(mt.Value.workflow_id, mt.Value.active)); // expose and deserialize all abstract members foreach (var fc in expose.FactDict.Values) // keys are Fact.Id { switch (fc.GetType().Name) { case "PointFact": PointFacts.Add(fc as PointFact); break; case "LineFact": LineFacts.Add(fc as LineFact); break; case "RayFact": RayFacts.Add(fc as RayFact); break; case "OnLineFact": OnLineFacts.Add(fc as OnLineFact); break; case "AngleFact": AngleFacts.Add(fc as AngleFact); break; default: throw new System.NotImplementedException(); } } } }