Newer
Older
using System.Collections.Generic;
using System.Reflection;
using System.IO;
using Newtonsoft.Json;
using System.Collections;
using UnityEngine;
using System.Linq;
Marco Zimmer
committed
using static CommunicationEvents;
//TODO: MMT: move some functionality there
//TODO: consequent!= samestep != dependent
//PERF: avoid string as key (general: allocations & dict: hash -> colission? -> strcmp[!])
Marco Zimmer
committed
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;
Marco Zimmer
committed
// InvokeEvents?
// TODO? SE: better seperation
// Label Managment; Communicates with Facts
protected internal int MaxLabelId = 0;
protected internal SortedSet<int> UnusedLabelIds = new SortedSet<int>();
Marco Zimmer
committed
private string path = null;
Marco Zimmer
committed
private static List<Directories>
hierState = new List<Directories> { Directories.FactStateMachines };
protected internal struct stepnote
// 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;
Marco Zimmer
committed
// 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;
}
}
Marco Zimmer
committed
public FactOrganizer(bool invoke = false)
Marco Zimmer
committed
FactDict = new Dictionary<string, Fact>();
this.invoke = invoke;
}
private static void FactOrganizerFromPublic(ref FactOrganizer set, PublicFactOrganizer exposed, bool invoke, out Dictionary<string, string> old_to_new)
{
// TODO: other strategy needed when MMT save/load supported
// map old URIs to new ones
// 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);
Marco Zimmer
committed
Marco Zimmer
committed
set.invoke = invoke;
set.MaxLabelId = exposed.MaxLabelId;
set.UnusedLabelIds = exposed.UnusedLabelIds;
Marco Zimmer
committed
set.FactDict = new Dictionary<string, Fact>();
// work Workflow
foreach (var sn in exposed.Workflow)
{
if (sn.creation)
// Add
{
Fact add;
if (old_to_new.ContainsKey(sn.Id))
Marco Zimmer
committed
add = set.FactDict[old_to_new[sn.Id]];
else
{
Fact old_Fact = old_FactDict[sn.Id];
add = old_Fact.GetType()
.GetConstructor(new Type[] { old_Fact.GetType(), old_to_new.GetType(), typeof(FactOrganizer) })
Marco Zimmer
committed
.Invoke(new object[] { old_Fact, old_to_new, set })
as Fact;
old_to_new.Add(sn.Id, add.Id);
}
Marco Zimmer
committed
set.Add(add, out _, sn.samestep);
}
else if(old_to_new.ContainsKey(sn.Id))
// Remove
{
Marco Zimmer
committed
Fact remove = set.FactDict[old_to_new[sn.Id]];
set.Remove(remove, sn.samestep);
}
}
// set un-redo state
Marco Zimmer
committed
while (set.backlog < exposed.backlog)
set.undo();
Marco Zimmer
committed
set.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);
}
Marco Zimmer
committed
public Fact this[string id]
{
get { return FactDict[id]; }
Marco Zimmer
committed
}
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
{
Marco Zimmer
committed
if (exact = FactDict.ContainsKey(search.Id))
{
Marco Zimmer
committed
found = search.Id;
return true;
}
Marco Zimmer
committed
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
if (note.samestep)
// update steplink of steproot
{
stepnote tmp = Workflow[note.steplink];
tmp.steplink = Workflow.Count + 1;
Workflow[note.steplink] = tmp;
}
Workflow.Add(note);
marker = Workflow.Count;
InvokeFactEvent(note.creation, note.Id);
}
private void PruneWorkflow()
// set current (displayed) state in stone; resets un-redo parameters
Marco Zimmer
committed
/*if (soft_resetted)
this.hardreset(false); // musn't clear
Marco Zimmer
committed
else*/ if (backlog > 0)
{
worksteps -= backlog;
backlog = 0;
for (int i = Workflow.Count - 1; i >= marker; i--)
{
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();
Marco Zimmer
committed
FactDict.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
{
Marco Zimmer
committed
soft_resetted = false;
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
// brand new Fact
Marco Zimmer
committed
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)
{
Marco Zimmer
committed
return this.Remove(value.Id, samestep);
public bool Remove(string key, bool samestep = false)
//no reset check needed (impossible state)
Marco Zimmer
committed
if (!FactDict.ContainsKey(key))
return false;
//TODO: see issue #58
safe_dependencies(key, out List<string> deletethis);
if(deletethis.Count > 0)
{
yeetusdeletus(deletethis, samestep);
}
// 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
{
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;
}
}
Marco Zimmer
committed
public void Clear()
// Does not Invoke RemoveFactEvent(s)!
{
Marco Zimmer
committed
FactDict.Clear();
MetaInf.Clear();
Workflow.Clear();
marker = 0;
worksteps = 0;
backlog = 0;
soft_resetted = false;
}
public void hardreset(bool invoke_event = true)
{
Marco Zimmer
committed
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();
}
Marco Zimmer
committed
public void store(string name, List<Directories> hierarchie = null, bool use_install_folder = false, bool force_write = true)
Marco Zimmer
committed
hierarchie ??= new List<Directories>();
hierarchie.AddRange(hierState.AsEnumerable());
Marco Zimmer
committed
string path_o = path;
path = CreatePathToFile(out bool exists, name, "JSON", hierarchie, use_install_folder);
Marco Zimmer
committed
hierarchie.RemoveRange(hierarchie.Count - hierState.Count, hierState.Count);
// 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)
Marco Zimmer
committed
if(!exists || force_write)
JSONManager.WriteToJsonFile(path, new PublicFactOrganizer(this), 0);
path = path_o;
public static bool load(ref FactOrganizer set, bool draw, string name, List<Directories> hierarchie, bool use_install_folder, out Dictionary<string, string> old_to_new)
Marco Zimmer
committed
hierarchie ??= new List<Directories>();
hierarchie.AddRange(hierState.AsEnumerable());
string path = CreatePathToFile(out bool loadable, name, "JSON", hierarchie, use_install_folder);
hierarchie.RemoveRange(hierarchie.Count - hierState.Count, hierState.Count);
if (!loadable)
return false;
PublicFactOrganizer de_json = JSONManager.ReadFromJsonFile<PublicFactOrganizer>(path);
FactOrganizerFromPublic(ref set, de_json, draw, out old_to_new);
Marco Zimmer
committed
set.path = path;
Marco Zimmer
committed
return true;
Marco Zimmer
committed
public static void delete(string name, List<Directories> hierarchie, bool use_install_folder)
{
hierarchie ??= new List<Directories>();
hierarchie.AddRange(hierState.AsEnumerable());
string path = CreatePathToFile(out bool _, name, "JSON", hierarchie, use_install_folder);
hierarchie.RemoveRange(hierarchie.Count - hierState.Count, hierState.Count);
delete(path);
}
public static void delete(string path)
{
if (File.Exists(path))
File.Delete(path);
}
public void delete()
{
delete(path);
}
public void Draw(bool draw_all = false)
// call this after assigning a stored instance in an empty world, that was not drawn
Marco Zimmer
committed
// TODO: communication with MMT
Marco Zimmer
committed
foreach (var key in FactDict.Keys)
{
// update active info if needed
Marco Zimmer
committed
meta info = MetaInf[key];
if (info.active)
{
info.active = false;
Marco Zimmer
committed
MetaInf[key] = info;
}
}
marker = 0;
var stop = draw_all ? worksteps : backlog;
backlog = worksteps;
while(backlog > stop)
redo();
Marco Zimmer
committed
public void Undraw(bool force_invoke = false)
{
foreach (var entry in FactDict)
{
if (force_invoke || (invoke && MetaInf[entry.Key].active))
CommunicationEvents.RemoveFactEvent.Invoke(entry.Value);
}
}
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]);
Marco Zimmer
committed
if (creation)
// undo freeLabel()
_ = FactDict[Id].Label;
else
FactDict[Id].freeAutoLabel();
}
Marco Zimmer
committed
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 List<List<string>>();
int MissingElementsCount = 0;
var activeList = FactDict.Values.Where(f => MetaInf[f.Id].active);
Marco Zimmer
committed
foreach (var ValidationSet in MinimalSolution.ValidationSet)
// check by MasterIds
// ALL Masters must relate
Marco Zimmer
committed
ValidationSet.MasterIDs.Select(URI => MinimalSolution[URI]);
activeList.Where(active => part_minimal.Contains(active, ValidationSet.Comparer.SetSearchRight()))
.ToList(); // needed for some reason
Marco Zimmer
committed
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 => MinimalSolution[URI]); // Get Facts
var part_consequential_solution =
activeList.Where(active => part_consequential_minimal.Contains(active, ValidationSet.Comparer.SetSearchRight()));
Solution_L.Last().Concat(part_consequential_solution.Select(fact => fact.Id).ToList());
MissingElementsCount += Convert.ToInt32(
part_consequential_solution.Count() == 0 && part_consequential_minimal.Count() != 0);
return MissingElementsCount == 0;
}
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
}
// 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;
// TODO? SE: better seperation
// Label Managment; Communicates with Facts
public new int MaxLabelId = 0;
public new SortedSet<int> UnusedLabelIds = new SortedSet<int>();
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
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;
MaxLabelId = expose.MaxLabelId;
UnusedLabelIds = expose.UnusedLabelIds;
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
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();
}
}
}