Implement proper algorithm for finding loops

master
D4VID 7 months ago
parent c5d7d47404
commit 66a3704ee4

@ -10,4 +10,3 @@ CriticalPathAnalyzer.CriticalPathAnalyzerTool:
Triggers:
- CriticalPathAnalyzer.AnalyzePathStart
- CriticalPathAnalyzer.AnalyzePathEnd
- CriticalPathAnalyzer.DisplayLoop

@ -16,9 +16,3 @@ CriticalPathAnalyzer.AnalyzePathEnd:
DefaultBinding:
Options:
- O
CriticalPathAnalyzer.DisplayLoop:
Heading: "CriticalPathAnalyzer"
DefaultBinding:
Options:
- P

@ -14,8 +14,3 @@ FancyInput.Trigger.CriticalPathAnalyzer.AnalyzePathEnd: "Select end of path"
FancyInput.Trigger.CriticalPathAnalyzer.AnalyzePathEnd.Description: """
Press to select the end of the path to be analyzed.
"""
FancyInput.Trigger.CriticalPathAnalyzer.DisplayLoop: "Display loop"
FancyInput.Trigger.CriticalPathAnalyzer.DisplayLoop.Description: """
Highlights only clusters that are causing a loop.
"""

@ -24,7 +24,6 @@ namespace CriticalPathAnalyzer.Client {
UITrigger.Back,
CriticalPathAnalyzerTrigger.AnalyzePathStart,
CriticalPathAnalyzerTrigger.AnalyzePathEnd,
CriticalPathAnalyzerTrigger.DisplayLoop,
};
/// <summary>
@ -44,8 +43,6 @@ namespace CriticalPathAnalyzer.Client {
CriticalPathAnalyzerTool.SelectPathStart();
} else if (CustomInput.DownThisFrame(CriticalPathAnalyzerTrigger.AnalyzePathEnd)) {
CriticalPathAnalyzerTool.SelectPathEnd();
} else if (CustomInput.DownThisFrame(CriticalPathAnalyzerTrigger.DisplayLoop)) {
CriticalPathAnalyzerTool.DisplayLoop();
}
}

@ -5,6 +5,5 @@ namespace CriticalPathAnalyzer.Client.Keybindings {
OpenPathAnalyzer,
AnalyzePathStart,
AnalyzePathEnd,
DisplayLoop,
}
}

@ -20,7 +20,7 @@ namespace CriticalPathAnalyzer.Client.Tool {
private static AnalyzePathResponse _response;
private static readonly OutlineData StartOutline = new OutlineData(new Color24(0x00ff00));
private static readonly OutlineData EndOutline = new OutlineData(new Color24(0xff0000));
private static readonly OutlineData EndOutline = new OutlineData(new Color24(0x00aaff));
public static void Init(ILogicLogger logger) {
_logger = logger;
@ -97,19 +97,6 @@ namespace CriticalPathAnalyzer.Client.Tool {
}
}
/// <summary>
/// Remove standard highlighting and only highlight clusters that form a loop.
/// </summary>
public static void DisplayLoop() {
if (_response == null) {
_logger.Error("Got no response - cannot display loop");
return;
}
PathHighlighter.RemoveHighlighting();
PathHighlighter.HighlightWires(_response.LoopingClusters);
}
/// <summary>
/// Send a request to the server to analyze the path between the selected start and end pegs.
/// </summary>
@ -141,10 +128,14 @@ namespace CriticalPathAnalyzer.Client.Tool {
_response = response;
_logger.Info($"Critical path length is: {_response.CriticalPathLength}");
PathHighlighter.RemoveHighlighting();
PathHighlighter.HighlightWires(_response.Clusters);
if (_response.LoopingClusters.Count > 0) {
PathHighlighter.HighlightWires(_response.LoopingClusters);
} else {
_logger.Info($"Critical path length is: {_response.CriticalPathLength}");
PathHighlighter.HighlightWires(_response.Clusters);
}
}
}
}

@ -7,6 +7,6 @@ namespace CriticalPathAnalyzer.Server.Tool {
public HashSet<Cluster> Clusters; // bidirectionally connected clusters without delay
public int Time; // what point in time has been this cluster last updated
public Dictionary<int, int> NextNodes; // next node index / delay between them
public HashSet<int> PrevNodeIndexes; // the last cluster that updated this one
public HashSet<int> PrevNodeIndexes; // node indexes that update this one
}
}

@ -57,18 +57,17 @@ namespace CriticalPathAnalyzer.Server.Tool {
var clusterNodes = new List<ClusterNode>();
var clusterToNodeMapping = new Dictionary<Cluster, int>(); // Cluster / index of node
var queue = new Queue<ClusterNode>();
var loopingNodes = new HashSet<int>();
// An input peg, only has a single cluster.
// An output peg however can be connected to multiple clusters.
// It only makes sense to then select all these clusters as primary cluster.
var collectedClusters = new HashSet<Cluster>();
CollectMainClusters(start, collectedClusters);
var startingClusters = new HashSet<Cluster>();
CollectMainClusters(start, startingClusters);
var endingClusters = new HashSet<Cluster>();
CollectMainClusters(end, endingClusters);
foreach (Cluster startingCluster in collectedClusters) {
foreach (Cluster startingCluster in startingClusters) {
// ignore already mapped clusters
if (clusterToNodeMapping.ContainsKey(startingCluster)) {
continue;
@ -93,12 +92,112 @@ namespace CriticalPathAnalyzer.Server.Tool {
queue.Enqueue(node);
}
logger.Info($"collected {collectedClusters.Count} main clusters");
logger.Info($"collected {startingClusters.Count} main clusters");
int iters = 0;
// Backup starting clusters for later
List<int> startingNodeIndexes = clusterNodes.Select(node => node.Index).ToList();
BuildClusterTree(queue, clusterToNodeMapping, clusterNodes);
List<ClusterNode> loopingNodes = FindLoops(clusterNodes);
logger.Info($"Found {loopingNodes.Count} looping nodes");
if (loopingNodes.Count == 0) {
PropagateTimeDelay(clusterNodes, startingNodeIndexes);
}
int criticalPathLength = -1;
if (endingClusters.Count > 0) {
// Attempt to get the critical path length
// If the ending cluster has not been found, return -1
Cluster endingCluster = endingClusters.First();
if (clusterToNodeMapping.TryGetValue(endingCluster, out int nodeIndex)) {
criticalPathLength = clusterNodes[nodeIndex].Time;
}
}
// Collect information about each cluster:
response = new AnalyzePathResponse() {
RequestGuid = requestGuid,
Clusters = clusterNodes.SelectMany(node =>
node.Clusters.Select(cluster => CollectClusterInformation(cluster, node.Time))).ToList(),
CriticalPathLength = criticalPathLength,
LoopingClusters = loopingNodes.SelectMany(loopingNode => loopingNode.Clusters
.Select(cluster => CollectClusterInformation(cluster, 0))).ToList(),
};
logger.Info("Trace end");
return true;
}
private static void PropagateTimeDelay(List<ClusterNode> clusterNodes, List<int> startingNodeIndexes) {
var queue = new Queue<int>(startingNodeIndexes);
while (queue.TryDequeue(out int index)) {
ClusterNode node = clusterNodes[index];
foreach ((int nextNodeIndex, int delay) in node.NextNodes) {
clusterNodes[nextNodeIndex].Time = node.Time + delay;
queue.Enqueue(nextNodeIndex);
}
}
}
private static List<ClusterNode> FindLoops(List<ClusterNode> clusterNodesOrig) {
List<ClusterNode> clusterNodes = clusterNodesOrig.Select(node => new ClusterNode() {
Index = node.Index,
Clusters = node.Clusters,
NextNodes = node.NextNodes.ToDictionary(),
PrevNodeIndexes = node.PrevNodeIndexes.ToHashSet(),
Time = node.Time,
}).ToList();
var queue = new Queue<ClusterNode>();
foreach (ClusterNode node in clusterNodes) {
if (node.PrevNodeIndexes.Count == 0) {
// source
queue.Enqueue(node);
}
if (node.NextNodes.Count == 0) {
// source
queue.Enqueue(node);
}
}
while (queue.TryDequeue(out ClusterNode node)) {
foreach ((int nextNodeIndex, int _) in node.NextNodes) {
ClusterNode nextNode = clusterNodes[nextNodeIndex];
nextNode.PrevNodeIndexes.Remove(node.Index);
if (nextNode.PrevNodeIndexes.Count == 0) {
queue.Enqueue(nextNode);
}
}
foreach (int prevNodeIndex in node.PrevNodeIndexes) {
ClusterNode prevNode = clusterNodes[prevNodeIndex];
prevNode.NextNodes.Remove(node.Index);
if (prevNode.NextNodes.Count == 0) {
queue.Enqueue(prevNode);
}
}
}
return clusterNodes.Where(node => node.PrevNodeIndexes.Count > 0 && node.NextNodes.Count > 0).ToList();
}
private static void BuildClusterTree(Queue<ClusterNode> queue, Dictionary<Cluster, int> clusterToNodeMapping,
List<ClusterNode> clusterNodes) {
ILogicLogger logger = CriticalPathAnalyzerServer.LoggerInstance;
int iterations = 0;
while (queue.TryDequeue(out ClusterNode node)) {
iters++;
if (iters > 10000) {
iterations++;
if (iterations % 1000 == 0) {
logger.Info($"{iterations} iterations");
}
if (iterations > 100000) {
logger.Error("Infinite iterations");
break;
}
@ -124,71 +223,36 @@ namespace CriticalPathAnalyzer.Server.Tool {
if (clusterToNodeMapping.TryGetValue(nextCluster, out int nextNodeIndex)) {
ClusterNode nextNode = clusterNodes[nextNodeIndex];
// only add the link, don't propagate
if (!nextNode.PrevNodeIndexes.Add(node.Index)) {
logger.Warn("Loop detected");
loopingNodes.Add(node.Index);
loopingNodes.Add(nextNode.Index);
// already been here - there is a cycle
// TODO: this doesn't work - if cluster A is updated by cluster B,
// TODO: and cluster B is updated by clusters C and D, it detects a cycle where isn't one
continue;
}
// Replace the time with a later one and continue
int timeDelay = 1;
nextNode.PrevNodeIndexes.Add(node.Index);
node.NextNodes.TryAdd(nextNodeIndex, 1);
nextNode.Time = node.Time + timeDelay;
queue.Enqueue(nextNode);
} else {
// var nextClusters = new HashSet<Cluster>();
// var twoWayConnectedClusters = new HashSet<Cluster>();
// GetLinkedClusters(cluster, nextClusters, twoWayConnectedClusters, GetFollowers);
int timeDelay = 1;
nextNodeIndex = clusterNodes.Count;
var nextNode = new ClusterNode() {
Index = nextNodeIndex,
Clusters = new HashSet<Cluster>() {nextCluster},
Time = node.Time + timeDelay,
Time = 0,
NextNodes = new Dictionary<int, int>(),
PrevNodeIndexes = new HashSet<int>() {node.Index},
};
clusterNodes.Add(nextNode);
node.NextNodes.TryAdd(nextNodeIndex, timeDelay);
node.NextNodes.TryAdd(nextNodeIndex, 1);
foreach (Cluster nextNodeCluster in nextNode.Clusters) {
clusterToNodeMapping.Add(nextNodeCluster, nextNodeIndex);
}
// propagate
queue.Enqueue(nextNode);
}
}
}
// Attempt to get the critical path length
// If the ending cluster has not been found, return -1
Cluster endingCluster = endingClusters.First();
int criticalPathLength = -1;
if (clusterToNodeMapping.TryGetValue(endingCluster, out int nodeIndex)) {
criticalPathLength = clusterNodes[nodeIndex].Time;
}
// Collect information about each cluster:
response = new AnalyzePathResponse() {
RequestGuid = requestGuid,
Clusters = clusterNodes.SelectMany(node =>
node.Clusters.Select(cluster => CollectClusterInformation(cluster, node.Time))).ToList(),
CriticalPathLength = criticalPathLength,
LoopingClusters = loopingNodes.SelectMany(loopingNodeIndex => clusterNodes[loopingNodeIndex].Clusters
.Select(cluster => CollectClusterInformation(cluster, 0))).ToList(),
// PerimeterComponents = perimeterComponents.ToList(),
// NextClusters = collectedNextClusters.Select(CollectClusterInformation).ToList(),
};
logger.Info("Trace end");
return true;
logger.Info($"Finished after {iterations} iterations");
}
@ -333,6 +397,10 @@ namespace CriticalPathAnalyzer.Server.Tool {
return details;
}
/// <param name="h">0 - 360</param>
/// <param name="s">0.0 - 1.0</param>
/// <param name="v">0.0 - 1.0</param>
/// <returns>0xrrggbb</returns>
private static int HsvToRgb(int h, float s, float v) {
s = Math.Clamp(s, 0, 1);
v = Math.Clamp(v, 0, 1);

@ -13,8 +13,6 @@ namespace CriticalPathAnalyzer.Shared.Packets.S2C {
[Key(2)] public int CriticalPathLength;
[Key(3)] public List<ClusterDetails> LoopingClusters;
// [Key(4)] public List<ComponentAddress> PerimeterComponents;
// [Key(5)] public List<ClusterDetails> NextClusters;
}
[MessagePackObject]

Loading…
Cancel
Save