Compare commits

...

4 Commits

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

@ -14,3 +14,9 @@ FancyInput.Trigger.CriticalPathAnalyzer.AnalyzePathEnd: "Select end of path"
FancyInput.Trigger.CriticalPathAnalyzer.AnalyzePathEnd.Description: """ FancyInput.Trigger.CriticalPathAnalyzer.AnalyzePathEnd.Description: """
Press to select the end of the path to be analyzed. Press to select the end of the path to be analyzed.
""" """
FancyInput.Trigger.CriticalPathAnalyzer.SelectNode: "Select node to be analyzed"
FancyInput.Trigger.CriticalPathAnalyzer.SelectNode.Description: """
Press to select a node to be analyzed.
Can only be used after obtaining circuit data from the server by analyzing a path.
"""

@ -1,7 +1,7 @@
ID: CriticalPathAnalyzer ID: CriticalPathAnalyzer
Name: CriticalPathAnalyzer Name: CriticalPathAnalyzer
Author: D4VID Author: D4VID
Version: 0.0.4 Version: 0.2.1
Priority: 0 Priority: 0
Dependencies: Dependencies:
- HarmonyForLogicWorld - HarmonyForLogicWorld

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

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

@ -18,12 +18,14 @@ namespace CriticalPathAnalyzer.Client.Tool {
private static Guid _currentRequestGuid = Guid.NewGuid(); private static Guid _currentRequestGuid = Guid.NewGuid();
private static AnalyzePathResponse _response; private static AnalyzePathResponse _response;
private static Node _selectedNode;
private static readonly OutlineData StartOutline = new OutlineData(new Color24(0x00ff00)); private static readonly OutlineData StartOutline = new OutlineData(new Color24(0x00ff00));
private static readonly OutlineData EndOutline = new OutlineData(new Color24(0x00aaff)); private static readonly OutlineData EndOutline = new OutlineData(new Color24(0x00aaff));
public static void Init(ILogicLogger logger) { public static void Init(ILogicLogger logger) {
_logger = logger; _logger = logger;
PathHighlighter.Init(logger);
} }
/// <summary> /// <summary>
@ -80,6 +82,35 @@ namespace CriticalPathAnalyzer.Client.Tool {
} }
} }
} }
/// <summary>
/// Try to select the start of the path.
/// </summary>
public static void SelectNodeToAnalyze() {
if (_response == null) {
_logger.Info("No circuit data");
// Cannot do anything without data
return;
}
PegAddress pegAddress = RayCastPeg();
if (!pegAddress.IsEmpty()) {
Node node = _response.Nodes.Find(n => n.Pegs.Contains(pegAddress));
if (node != null) {
PathHighlighter.RemoveHighlighting();
_logger.Info("Highlighting selected node");
PathHighlighter.HighlightAnalyzedNode(node);
_selectedNode = node;
} else {
_logger.Info("Peg not found in circuit data");
}
} else {
// Deselect
_logger.Info("Didn't hit anything");
PathHighlighter.RemoveHighlighting();
_selectedNode = null;
}
}
/// <summary> /// <summary>
/// Try to select the end of the path. /// Try to select the end of the path.
@ -129,12 +160,13 @@ namespace CriticalPathAnalyzer.Client.Tool {
_response = response; _response = response;
PathHighlighter.RemoveHighlighting(); PathHighlighter.RemoveHighlighting();
PathHighlighter.SetNodes(_response.Nodes);
if (_response.LoopingClusters.Count > 0) { if (_response.LoopingNodes.Count > 0) {
PathHighlighter.HighlightWires(_response.LoopingClusters); PathHighlighter.HighlightClusterNodes(_response.LoopingNodes);
} else { } else {
_logger.Info($"Critical path length is: {_response.CriticalPathLength}"); _logger.Info($"Critical path length is: {_response.CriticalPathLength}");
PathHighlighter.HighlightWires(_response.Clusters); PathHighlighter.HighlightClusterNodes(_response.Nodes);
} }
} }
} }

@ -1,33 +1,56 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using CriticalPathAnalyzer.Shared.Packets.S2C; using CriticalPathAnalyzer.Shared.Packets.S2C;
using JimmysUnityUtilities; using JimmysUnityUtilities;
using LogicAPI.Data; using LogicAPI.Data;
using LogicAPI.Services; using LogicAPI.Services;
using LogicLog;
using LogicWorld.Interfaces; using LogicWorld.Interfaces;
using LogicWorld.Outlines; using LogicWorld.Outlines;
namespace CriticalPathAnalyzer.Client.Tool { namespace CriticalPathAnalyzer.Client.Tool {
public class PathHighlighter { public class PathHighlighter {
private static List<ClusterDetails> _clusters = new List<ClusterDetails>(); private static ILogicLogger _logger;
private static List<Node> _nodes = new List<Node>();
private static List<WireAddress> _highlightedWires = new List<WireAddress>(); private static List<WireAddress> _highlightedWires = new List<WireAddress>();
public static void Init(ILogicLogger logger) {
_logger = logger;
}
public static void SetNodes(List<Node> nodes) {
_nodes = nodes;
}
public static void HighlightWires(List<ClusterDetails> clusters) { public static void HighlightClusterNodes(List<Node> nodes) {
_clusters = clusters; foreach (Node node in nodes) {
HighlightNode(node, HsvToRgb(node.Time * 20, 1.0f, 1.0f));
}
}
foreach (ClusterDetails clusterDetails in _clusters) { public static void HighlightAnalyzedNode(Node node) {
HighlightCluster(clusterDetails); _logger.Info($"Selected node: {node}");
HighlightNode(node, 0x00ff00);
foreach (int prevNodeIndex in node.PrevNodeIndexes) {
_logger.Info($"Highlighting prev node index {prevNodeIndex}");
HighlightNode(_nodes[prevNodeIndex], 0xff0000);
}
foreach ((int nextNodeIndex, int delay) in node.NextNodes) {
_logger.Info($"Highlighting next node index {nextNodeIndex}");
HighlightNode(_nodes[nextNodeIndex], 0x0000ff);
} }
} }
/// <summary> /// <summary>
/// Highlight all pegs and wires of the given cluster. /// Highlight all pegs and wires of the given cluster.
/// </summary> /// </summary>
private static void HighlightCluster(ClusterDetails cluster) { private static void HighlightNode(Node node, int color) {
IWorldData world = Instances.MainWorld.Data; IWorldData world = Instances.MainWorld.Data;
var outline = new OutlineData(new Color24(cluster.Color)); var outline = new OutlineData(new Color24(color));
foreach (ComponentAddress address in cluster.ConnectingComponents) { foreach (ComponentAddress address in node.ConnectingComponents) {
if (!world.Contains(address)) { if (!world.Contains(address)) {
continue; continue;
} }
@ -35,7 +58,7 @@ namespace CriticalPathAnalyzer.Client.Tool {
Outliner.Outline(address, outline); Outliner.Outline(address, outline);
} }
foreach (PegAddress pegAddress in cluster.Pegs) { foreach (PegAddress pegAddress in node.Pegs) {
if (!world.Contains(pegAddress.ComponentAddress)) { if (!world.Contains(pegAddress.ComponentAddress)) {
continue; continue;
} }
@ -63,30 +86,80 @@ namespace CriticalPathAnalyzer.Client.Tool {
} }
public static void RemoveHighlighting() { public static void RemoveHighlighting() {
if (_clusters == null) { if (_nodes == null) {
return; return;
} }
foreach (ClusterDetails cluster in _clusters) { foreach (Node cluster in _nodes) {
UnhighlightCluster(cluster); UnhighlightNode(cluster);
} }
foreach (WireAddress wireAddress in _highlightedWires) { foreach (WireAddress wireAddress in _highlightedWires) {
Outliner.RemoveOutline(wireAddress); Outliner.RemoveOutline(wireAddress);
} }
_clusters = null;
_highlightedWires.Clear(); _highlightedWires.Clear();
} }
private static void UnhighlightCluster(ClusterDetails cluster) { private static void UnhighlightNode(Node node) {
foreach (PegAddress address in cluster.Pegs) { foreach (PegAddress address in node.Pegs) {
Outliner.RemoveOutline(address); Outliner.RemoveOutline(address);
} }
foreach (ComponentAddress address in cluster.ConnectingComponents) { foreach (ComponentAddress address in node.ConnectingComponents) {
Outliner.RemoveOutline(address); Outliner.RemoveOutline(address);
} }
} }
/// <summary>
/// Convert HSB to RGB
/// </summary>
/// <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);
h = (h % 360 + 360) % 360; // Ensure hue is within 0-359 range
float c = v * s;
float x = c * (1 - Math.Abs((h / 60.0f) % 2 - 1));
float m = v - c;
float r, g, b;
if (h < 60) {
r = c;
g = x;
b = 0;
} else if (h < 120) {
r = x;
g = c;
b = 0;
} else if (h < 180) {
r = 0;
g = c;
b = x;
} else if (h < 240) {
r = 0;
g = x;
b = c;
} else if (h < 300) {
r = x;
g = 0;
b = c;
} else {
r = c;
g = 0;
b = x;
}
int rInt = (byte) ((r + m) * 255);
int gInt = (byte) ((g + m) * 255);
int bInt = (byte) ((b + m) * 255);
return (rInt << 16) | (gInt << 8) | bInt;
}
} }
} }

@ -3,10 +3,29 @@ using LogicWorld.Server.Circuitry;
namespace CriticalPathAnalyzer.Server.Tool { namespace CriticalPathAnalyzer.Server.Tool {
public class ClusterNode { public class ClusterNode {
public int Index; // "identifier" /// <summary>
public HashSet<Cluster> Clusters; // bidirectionally connected clusters without delay /// Identifier
public int Time; // what point in time has been this cluster last updated /// </summary>
public Dictionary<int, int> NextNodes; // next node index / delay between them public int Index;
public HashSet<int> PrevNodeIndexes; // node indexes that update this one
/// <summary>
/// All bidirectionally connected clusters without delay that form this node
/// </summary>
public HashSet<Cluster> Clusters;
/// <summary>
/// Next node index / delay between them
/// </summary>
public Dictionary<int, int> NextNodes;
/// <summary>
/// Node indexes that update this one
/// </summary>
public HashSet<int> PrevNodeIndexes;
/// <summary>
/// After how many ticks has been this cluster last updated
/// </summary>
public int Time;
} }
} }

@ -109,14 +109,16 @@ namespace CriticalPathAnalyzer.Server.Tool {
} }
} }
foreach (ClusterNode node in clusterNodes) {
FillNodeInformation(node);
}
// Collect information about each cluster: // Collect information about each cluster:
response = new AnalyzePathResponse() { response = new AnalyzePathResponse() {
RequestGuid = requestGuid, RequestGuid = requestGuid,
Clusters = clusterNodes.SelectMany(node => Nodes = clusterNodes.Select(FillNodeInformation).ToList(),
node.Clusters.Select(cluster => CollectClusterInformation(cluster, node.Time))).ToList(),
CriticalPathLength = criticalPathLength, CriticalPathLength = criticalPathLength,
LoopingClusters = loopingNodes.SelectMany(loopingNode => loopingNode.Clusters LoopingNodes = loopingNodes.Select(FillNodeInformation).ToList(),
.Select(cluster => CollectClusterInformation(cluster, 0))).ToList(),
}; };
logger.Info("Trace end"); logger.Info("Trace end");
@ -133,6 +135,7 @@ namespace CriticalPathAnalyzer.Server.Tool {
if (iterations % 1000 == 0) { if (iterations % 1000 == 0) {
CriticalPathAnalyzerServer.LoggerInstance.Info($"{iterations} propagation iterations"); CriticalPathAnalyzerServer.LoggerInstance.Info($"{iterations} propagation iterations");
} }
ClusterNode node = clusterNodes[index]; ClusterNode node = clusterNodes[index];
foreach ((int nextNodeIndex, int delay) in node.NextNodes) { foreach ((int nextNodeIndex, int delay) in node.NextNodes) {
clusterNodes[nextNodeIndex].Time = node.Time + delay; clusterNodes[nextNodeIndex].Time = node.Time + delay;
@ -169,6 +172,7 @@ namespace CriticalPathAnalyzer.Server.Tool {
if (iterations % 1000 == 0) { if (iterations % 1000 == 0) {
CriticalPathAnalyzerServer.LoggerInstance.Info($"{iterations} loop iterations"); CriticalPathAnalyzerServer.LoggerInstance.Info($"{iterations} loop iterations");
} }
foreach ((int nextNodeIndex, int _) in node.NextNodes) { foreach ((int nextNodeIndex, int _) in node.NextNodes) {
ClusterNode nextNode = clusterNodes[nextNodeIndex]; ClusterNode nextNode = clusterNodes[nextNodeIndex];
nextNode.PrevNodeIndexes.Remove(node.Index); nextNode.PrevNodeIndexes.Remove(node.Index);
@ -250,24 +254,40 @@ namespace CriticalPathAnalyzer.Server.Tool {
logger.Info($"Finished after {iterations} iterations"); logger.Info($"Finished after {iterations} iterations");
} }
private static HashSet<Cluster> GetClustersConnectedThroughRelays(Cluster cluster) { private static HashSet<Cluster> GetClustersConnectedThroughRelays(Cluster origin) {
var clusters = new HashSet<Cluster>() {cluster}; var clusters = new HashSet<Cluster> {origin};
foreach (InputPeg inputPeg in cluster.ConnectedInputs) { var queue = new Queue<Cluster>();
LogicComponent component = inputPeg.LogicComponent; queue.Enqueue(origin);
if (component == null) {
// These are regular pegs, they are not attached to any logic component, skip them
continue;
}
if (component.GetType() == typeof(Relay)) { while (queue.TryDequeue(out Cluster cluster)) {
// Collect all next clusters connected via a relay (from the control input side) foreach (InputPeg inputPeg in cluster.ConnectedInputs) {
if (inputPeg.Address == component.Inputs[1].Address) { LogicComponent component = inputPeg.LogicComponent;
// one of the sides if (component == null) {
// TODO: need to recursively check for further relays // These are regular pegs, they are not attached to any logic component, skip them
CollectPegClusters(component.Inputs[2].Address, clusters); continue;
} else if (inputPeg.Address == component.Inputs[2].Address) { }
// the other side
CollectPegClusters(component.Inputs[1].Address, clusters); if (component.GetType() == typeof(Relay)) {
// Collect all next clusters connected via a relay (from the control input side)
if (inputPeg.Address == component.Inputs[1].Address) {
// one of the sides
if (component.Inputs[2].Address.IsInputAddress(out InputAddress inputAddress)) {
Cluster nextCluster = GetClusterAt(inputAddress);
if (clusters.Add(nextCluster)) {
// found a new cluster, continue searching
queue.Enqueue(nextCluster);
}
}
} else if (inputPeg.Address == component.Inputs[2].Address) {
// the other side
if (component.Inputs[1].Address.IsInputAddress(out InputAddress inputAddress)) {
Cluster nextCluster = GetClusterAt(inputAddress);
if (clusters.Add(nextCluster)) {
// found a new cluster, continue searching
queue.Enqueue(nextCluster);
}
}
}
} }
} }
} }
@ -381,78 +401,37 @@ namespace CriticalPathAnalyzer.Server.Tool {
} }
private static ClusterDetails CollectClusterInformation(Cluster cluster, int time) { /// <summary>
var details = new ClusterDetails { /// Obtain additional information about the clusters:
/// Collect all their pegs and sockets.
/// </summary>
private static Node FillNodeInformation(ClusterNode node) {
var clientNode = new Node() {
Index = node.Index,
NextNodes = node.NextNodes,
PrevNodeIndexes = node.PrevNodeIndexes,
Time = node.Time,
Pegs = new List<PegAddress>(), Pegs = new List<PegAddress>(),
ConnectingComponents = new List<ComponentAddress>(), ConnectingComponents = new List<ComponentAddress>(),
Color = HsvToRgb(time * 20, 1, 1),
}; };
// Two lists are never null, according to how it is created and used: foreach (Cluster cluster in node.Clusters) {
IReadOnlyList<InputPeg> inputPegs = cluster.ConnectedInputs; // Two lists are never null, according to how it is created and used:
IReadOnlyList<OutputPeg> outputPegs = cluster.ConnectedOutputs; IReadOnlyList<InputPeg> inputPegs = cluster.ConnectedInputs;
IReadOnlyList<OutputPeg> outputPegs = cluster.ConnectedOutputs;
foreach (InputPeg peg in inputPegs) { foreach (InputPeg peg in inputPegs) {
details.Pegs.Add(peg.Address); clientNode.Pegs.Add(peg.Address);
if (peg.SecretLinks != null && peg.SecretLinks.Any()) { if (peg.SecretLinks != null && peg.SecretLinks.Any()) {
// Socket // Socket
details.ConnectingComponents.Add(peg.Address.ComponentAddress); clientNode.ConnectingComponents.Add(peg.Address.ComponentAddress);
}
} }
}
foreach (OutputPeg peg in outputPegs) { clientNode.Pegs.AddRange(outputPegs.Select(peg => peg.Address));
details.Pegs.Add(peg.Address);
} }
return details; return clientNode;
}
/// <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);
h = (h % 360 + 360) % 360; // Ensure hue is within 0-359 range
float c = v * s;
float x = c * (1 - Math.Abs((h / 60.0f) % 2 - 1));
float m = v - c;
float r, g, b;
if (h < 60) {
r = c;
g = x;
b = 0;
} else if (h < 120) {
r = x;
g = c;
b = 0;
} else if (h < 180) {
r = 0;
g = c;
b = x;
} else if (h < 240) {
r = 0;
g = x;
b = c;
} else if (h < 300) {
r = x;
g = 0;
b = c;
} else {
r = c;
g = 0;
b = x;
}
int rInt = (byte) ((r + m) * 255);
int gInt = (byte) ((g + m) * 255);
int bInt = (byte) ((b + m) * 255);
return (rInt << 16) | (gInt << 8) | bInt;
} }
} }
} }

@ -8,25 +8,42 @@ namespace CriticalPathAnalyzer.Shared.Packets.S2C {
[MessagePackObject] [MessagePackObject]
public class AnalyzePathResponse : Packet { public class AnalyzePathResponse : Packet {
[Key(0)] public Guid RequestGuid; [Key(0)] public Guid RequestGuid;
[Key(1)] public List<ClusterDetails> Clusters; [Key(1)] public List<Node> Nodes;
[Key(2)] public int CriticalPathLength; [Key(2)] public int CriticalPathLength;
[Key(3)] public List<Node> LoopingNodes;
[Key(3)] public List<ClusterDetails> LoopingClusters;
} }
[MessagePackObject] [MessagePackObject]
public sealed class ClusterDetails { public sealed class Node {
/// <summary>
/// Identifier
/// </summary>
[Key(0)] public int Index;
/// <summary>
/// Next node index / delay between them
/// </summary>
[Key(1)] public Dictionary<int, int> NextNodes;
/// <summary>
/// Node indexes that update this one
/// </summary>
[Key(2)] public HashSet<int> PrevNodeIndexes;
/// <summary>
/// After how many ticks has been this cluster last updated
/// </summary>
[Key(3)] public int Time;
/// <summary> /// <summary>
/// List of all pegs inside the cluster /// List of all pegs inside the cluster
/// </summary> /// </summary>
[Key(0)] public List<PegAddress> Pegs; [Key(4)] public List<PegAddress> Pegs;
/// <summary> /// <summary>
/// Sockets /// Sockets
/// </summary> /// </summary>
[Key(1)] public List<ComponentAddress> ConnectingComponents; [Key(5)] public List<ComponentAddress> ConnectingComponents;
[Key(3)] public int Color;
} }
} }
Loading…
Cancel
Save