Compare commits

...

3 Commits

@ -0,0 +1,2 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/UserDictionary/Words/=Phasic/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

@ -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.
"""

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

@ -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();
if (_response.LoopingClusters.Count > 0) {
PathHighlighter.HighlightWires(_response.LoopingClusters);
} else {
_logger.Info($"Critical path length is: {_response.CriticalPathLength}");
PathHighlighter.HighlightWires(_response.Clusters);
}
}
}
}

@ -9,7 +9,6 @@ using LogicWorld.Outlines;
namespace CriticalPathAnalyzer.Client.Tool {
public class PathHighlighter {
private static List<ClusterDetails> _clusters = new List<ClusterDetails>();
private static List<ComponentAddress> _highlightedComponents = new List<ComponentAddress>();
private static List<WireAddress> _highlightedWires = new List<WireAddress>();
public static void HighlightWires(List<ClusterDetails> clusters) {
@ -18,22 +17,6 @@ namespace CriticalPathAnalyzer.Client.Tool {
foreach (ClusterDetails clusterDetails in _clusters) {
HighlightCluster(clusterDetails);
}
// IWorldData world = Instances.MainWorld.Data;
// foreach (ComponentAddress componentAddress in response.PerimeterComponents) {
// if (!world.Contains(componentAddress)) {
// continue;
// }
//
// Outliner.Outline(componentAddress, PerimeterColor);
// _highlightedComponents.Add(componentAddress);
// }
// _clusters.AddRange(response.NextClusters); // add to the collection to unhighlight
// foreach (ClusterDetails clusterDetails in response.NextClusters) {
// HighlightCluster(clusterDetails, NextColor);
// }
}
/// <summary>
@ -52,14 +35,6 @@ namespace CriticalPathAnalyzer.Client.Tool {
Outliner.Outline(address, outline);
}
foreach (ComponentAddress address in cluster.LinkingComponents) {
if (!world.Contains(address)) {
continue;
}
Outliner.Outline(address, outline);
}
foreach (PegAddress pegAddress in cluster.Pegs) {
if (!world.Contains(pegAddress.ComponentAddress)) {
continue;
@ -100,13 +75,8 @@ namespace CriticalPathAnalyzer.Client.Tool {
Outliner.RemoveOutline(wireAddress);
}
foreach (ComponentAddress componentAddress in _highlightedComponents) {
Outliner.RemoveOutline(componentAddress);
}
_clusters = null;
_highlightedWires.Clear();
_highlightedComponents.Clear();
}
private static void UnhighlightCluster(ClusterDetails cluster) {
@ -117,10 +87,6 @@ namespace CriticalPathAnalyzer.Client.Tool {
foreach (ComponentAddress address in cluster.ConnectingComponents) {
Outliner.RemoveOutline(address);
}
foreach (ComponentAddress address in cluster.LinkingComponents) {
Outliner.RemoveOutline(address);
}
}
}
}

@ -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
}
}

@ -8,15 +8,13 @@ using LogicAPI.Data;
using LogicAPI.Server.Components;
using LogicAPI.Services;
using LogicLog;
using LogicWorld.LogicCode;
using LogicWorld.Server.Circuitry;
namespace CriticalPathAnalyzer.Server.Tool {
public class ServerPathTracer {
// Reflection/Delegate access helpers:
private static readonly Func<InputPeg, Cluster> GetCluster;
private static readonly Func<Cluster, ClusterLinker> GetLinker;
private static readonly Func<ClusterLinker, List<ClusterLinker>> GetFollowers;
// Services needed to lookup wires/pegs:
private static readonly ICircuitryManager Circuits;
@ -26,12 +24,6 @@ namespace CriticalPathAnalyzer.Server.Tool {
GetCluster = Delegator.createPropertyGetter<InputPeg, Cluster>(
Properties.getPrivate(typeof(InputPeg), "Cluster")
);
GetLinker = Delegator.createFieldGetter<Cluster, ClusterLinker>(
Fields.getPrivate(typeof(Cluster), "Linker")
);
GetFollowers = Delegator.createFieldGetter<ClusterLinker, List<ClusterLinker>>(
Fields.getPrivate(typeof(ClusterLinker), "LinkedFollowers")
);
Circuits = ServiceGetter.getService<ICircuitryManager>();
World = ServiceGetter.getService<IWorldData>();
@ -57,18 +49,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>();
CollectPegClusters(start, startingClusters);
var endingClusters = new HashSet<Cluster>();
CollectMainClusters(end, endingClusters);
CollectPegClusters(end, endingClusters);
foreach (Cluster startingCluster in collectedClusters) {
foreach (Cluster startingCluster in startingClusters) {
// ignore already mapped clusters
if (clusterToNodeMapping.ContainsKey(startingCluster)) {
continue;
@ -80,7 +71,7 @@ namespace CriticalPathAnalyzer.Server.Tool {
int index = clusterNodes.Count;
var node = new ClusterNode() {
Index = index,
Clusters = new HashSet<Cluster>() {startingCluster},
Clusters = GetClustersConnectedThroughRelays(startingCluster),
Time = 0,
PrevNodeIndexes = new HashSet<int>(),
NextNodes = new Dictionary<int, int>(),
@ -93,104 +84,237 @@ namespace CriticalPathAnalyzer.Server.Tool {
queue.Enqueue(node);
}
logger.Info($"collected {collectedClusters.Count} main clusters");
logger.Info($"collected {startingClusters.Count} main clusters");
// 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);
int iters = 0;
int iterations = 0;
while (queue.TryDequeue(out int index)) {
iterations++;
if (iterations % 1000 == 0) {
CriticalPathAnalyzerServer.LoggerInstance.Info($"{iterations} propagation iterations");
}
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);
}
}
int iterations = 0;
while (queue.TryDequeue(out ClusterNode node)) {
iterations++;
if (iterations % 1000 == 0) {
CriticalPathAnalyzerServer.LoggerInstance.Info($"{iterations} loop iterations");
}
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;
}
// var perimeterComponents = new HashSet<ComponentAddress>();
var collectedNextClusters = new HashSet<Cluster>();
var collectedNextInstantClusters = new HashSet<Cluster>();
foreach (Cluster cluster in node.Clusters) {
foreach (InputPeg inputPeg in cluster.ConnectedInputs) {
if (inputPeg.LogicComponent == null) {
LogicComponent component = inputPeg.LogicComponent;
if (component == null) {
// These are regular pegs, they are not attached to any logic component, skip them
continue;
}
// perimeterComponents.Add(inputPeg.LogicComponent.Address);
foreach (IOutputPeg outputPeg in inputPeg.LogicComponent.Outputs) {
CollectMainClusters(outputPeg.Address, collectedNextClusters);
if (component.GetType() == typeof(Delayer)) {
// Special handling of the delayer
var delayer = (Delayer) component;
//TODO: delayer.Data.DelayLengthInTicks;
CollectPegClusters(delayer.Outputs[0].Address, collectedNextClusters);
} else if (component.GetType() == typeof(Relay)) {
// Collect all next clusters connected via a relay (from the control input side)
if (inputPeg.Address == component.Inputs[0].Address) {
// the top control input
CollectPegClusters(component.Inputs[1].Address, collectedNextClusters);
CollectPegClusters(component.Inputs[2].Address, collectedNextClusters);
}
} else {
// Collect all next cluster connected via normal components
foreach (IOutputPeg outputPeg in component.Outputs) {
CollectPegClusters(outputPeg.Address, collectedNextClusters);
}
// Collect all next clusters connected via fast buffers
if (inputPeg.OneWayPhasicLinksFollowers != null) {
foreach (InputPeg followerPeg in inputPeg.OneWayPhasicLinksFollowers) {
CollectPegClusters(followerPeg.Address, collectedNextInstantClusters);
}
}
}
}
}
LinkNextClusters(queue, clusterToNodeMapping, clusterNodes, collectedNextInstantClusters, node, 0);
LinkNextClusters(queue, clusterToNodeMapping, clusterNodes, collectedNextClusters, node, 1);
}
logger.Info($"Finished after {iterations} iterations");
}
private static HashSet<Cluster> GetClustersConnectedThroughRelays(Cluster cluster) {
var clusters = new HashSet<Cluster>() {cluster};
foreach (InputPeg inputPeg in cluster.ConnectedInputs) {
LogicComponent component = inputPeg.LogicComponent;
if (component == null) {
// These are regular pegs, they are not attached to any logic component, skip them
continue;
}
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
// TODO: need to recursively check for further relays
CollectPegClusters(component.Inputs[2].Address, clusters);
} else if (inputPeg.Address == component.Inputs[2].Address) {
// the other side
CollectPegClusters(component.Inputs[1].Address, clusters);
}
}
}
return clusters;
}
private static void LinkNextClusters(Queue<ClusterNode> queue, Dictionary<Cluster, int> clusterToNodeMapping,
List<ClusterNode> clusterNodes, HashSet<Cluster> collectedNextClusters, ClusterNode node, int delay) {
foreach (Cluster nextCluster in collectedNextClusters) {
// ignore already mapped clusters
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
nextNode.PrevNodeIndexes.Add(node.Index);
node.NextNodes.TryAdd(nextNodeIndex, delay);
} else {
HashSet<Cluster> nextClusters = GetClustersConnectedThroughRelays(nextCluster);
if (nextClusters.Any(clusterToNodeMapping.ContainsKey)) {
// already discovered cluster node
continue;
}
// Replace the time with a later one and continue
int timeDelay = 1;
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,
Clusters = nextClusters,
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, delay);
foreach (Cluster nextNodeCluster in nextNode.Clusters) {
clusterToNodeMapping.Add(nextNodeCluster, nextNodeIndex);
clusterToNodeMapping.TryAdd(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;
}
/// <summary>
/// Check if the peg exists in the world
@ -224,18 +348,13 @@ namespace CriticalPathAnalyzer.Server.Tool {
return cluster;
}
private static bool GetLinkerAt(Cluster cluster, out ClusterLinker linker) {
linker = GetLinker(cluster);
return linker != null;
}
/// <summary>
/// An output peg can be connected to multiple clusters. Collect all of them.
/// </summary>
/// <param name="pegAddress">Address of the peg</param>
/// <param name="primaryClusters">Found clusters get added into this hash set</param>
/// <exception cref="Exception">A wire of the peg is not in the world</exception>
private static void CollectMainClusters(PegAddress pegAddress, HashSet<Cluster> primaryClusters) {
private static void CollectPegClusters(PegAddress pegAddress, HashSet<Cluster> primaryClusters) {
if (pegAddress.IsInputAddress(out InputAddress inputAddress)) {
primaryClusters.Add(GetClusterAt(inputAddress));
} else {
@ -261,48 +380,11 @@ namespace CriticalPathAnalyzer.Server.Tool {
}
}
private static void GetLinkedClusters(
Cluster startingPoint,
HashSet<Cluster> oneWayConnectedClusters,
HashSet<Cluster> twoWayConnectedClusters,
Func<ClusterLinker, List<ClusterLinker>> linkedLinkerGetter
) {
var clustersToProcess = new Queue<ClusterLinker>();
if (!GetLinkerAt(startingPoint, out ClusterLinker startingLinker)) {
return; // No linker on this cluster => no link => nothing to collect
}
clustersToProcess.Enqueue(startingLinker);
twoWayConnectedClusters.Add(startingPoint);
// While the starting cluster is no source, the algorithm needs to skip it when encountered.
while (clustersToProcess.TryDequeue(out ClusterLinker linkerToCheck)) {
List<ClusterLinker> listOfLinkedLinkers = linkedLinkerGetter(linkerToCheck); // Is never null.
foreach (ClusterLinker linkedLinker in listOfLinkedLinkers) {
Cluster clusterOfLinkedLinker = linkedLinker.ClusterBeingLinked; // Should never be null.
List<ClusterLinker> listOfBackLinkers = linkedLinkerGetter(linkedLinker); // Is never null.
if (listOfBackLinkers.Contains(linkerToCheck)) {
// bidirectional - a relay
// TODO: will not find a relay connection if the relay is turned off
if (twoWayConnectedClusters.Add(clusterOfLinkedLinker)) {
// Element was not yet present in the array, so keep looking into it!
clustersToProcess.Enqueue(linkedLinker);
}
} else {
// unidirectional - a fast buffer
oneWayConnectedClusters.Add(clusterOfLinkedLinker);
// stop propagation
}
}
}
twoWayConnectedClusters.Remove(startingPoint);
}
private static ClusterDetails CollectClusterInformation(Cluster cluster, int time) {
var details = new ClusterDetails {
Pegs = new List<PegAddress>(),
ConnectingComponents = new List<ComponentAddress>(),
LinkingComponents = new List<ComponentAddress>(),
Color = HsvToRgb(time * 20, 1, 1),
};
@ -316,14 +398,6 @@ namespace CriticalPathAnalyzer.Server.Tool {
// Socket
details.ConnectingComponents.Add(peg.Address.ComponentAddress);
}
if ((peg.PhasicLinks != null && peg.PhasicLinks.Any())
|| (peg.OneWayPhasicLinksFollowers != null && peg.OneWayPhasicLinksFollowers.Any())
|| (peg.OneWayPhasicLinksLeaders != null && peg.OneWayPhasicLinksLeaders.Any())
) {
// Relay / fast buffer
details.LinkingComponents.Add(peg.Address.ComponentAddress);
}
}
foreach (OutputPeg peg in outputPegs) {
@ -333,6 +407,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]
@ -29,11 +27,6 @@ namespace CriticalPathAnalyzer.Shared.Packets.S2C {
/// </summary>
[Key(1)] public List<ComponentAddress> ConnectingComponents;
/// <summary>
/// Relays and fast buffers
/// </summary>
[Key(2)] public List<ComponentAddress> LinkingComponents;
[Key(3)] public int Color;
}
}
Loading…
Cancel
Save