parent
bc74dcd6ee
commit
5b542dbcc8
@ -0,0 +1,111 @@
|
||||
using System.Collections.Generic;
|
||||
using CriticalPathAnalyzer.client.tool;
|
||||
using CriticalPathAnalyzer.Shared.Packets.S2C;
|
||||
using LogicAPI.Data;
|
||||
using LogicAPI.Services;
|
||||
using LogicWorld.Interfaces;
|
||||
using LogicWorld.Outlines;
|
||||
|
||||
namespace CriticalPathAnalyzer.Client.Tool {
|
||||
public class PathHighLighter {
|
||||
private static List<ClusterDetails> _clusters;
|
||||
|
||||
public static void HighlightWires(AnalyzePathResponse response) {
|
||||
_clusters = response.SelectedClusters;
|
||||
|
||||
IWorldData world = Instances.MainWorld.Data;
|
||||
|
||||
foreach (ClusterDetails clusterDetails in _clusters) {
|
||||
foreach (ComponentAddress address in clusterDetails.ConnectingComponents) {
|
||||
if (!world.Contains(address)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Outliner.Outline(address, WireTracerColors.primaryConnected);
|
||||
}
|
||||
|
||||
foreach (ComponentAddress address in clusterDetails.LinkingComponents) {
|
||||
if (!world.Contains(address)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Outliner.Outline(address, WireTracerColors.linking);
|
||||
}
|
||||
|
||||
clusterDetails.HighlightedWires = new List<WireAddress>();
|
||||
clusterDetails.HighlightedOutputWires = new List<WireAddress>();
|
||||
|
||||
foreach (PegAddress pegAddress in clusterDetails.Pegs) {
|
||||
if (!world.Contains(pegAddress.ComponentAddress)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!pegAddress.IsInputAddress()) {
|
||||
Outliner.Outline(pegAddress, WireTracerColors.primaryOutput);
|
||||
continue;
|
||||
}
|
||||
|
||||
Outliner.Outline(pegAddress, WireTracerColors.primaryNormal);
|
||||
|
||||
HashSet<WireAddress> wires = Instances.MainWorld.Data.LookupPegWires(pegAddress);
|
||||
if (wires == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (WireAddress wireAddress in wires) {
|
||||
Wire wire = Instances.MainWorld.Data.Lookup(wireAddress);
|
||||
if (wire == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We do not collect wires from output pegs. So if the first is an output peg, the other side must be an input -> collect.
|
||||
if (wire.Point1 == pegAddress || !wire.Point1.IsInputAddress()) {
|
||||
if (wire.Point1.IsInputAddress() && wire.Point2.IsInputAddress()) {
|
||||
clusterDetails.HighlightedWires.Add(wireAddress);
|
||||
} else {
|
||||
clusterDetails.HighlightedOutputWires.Add(wireAddress);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (WireAddress address in clusterDetails.HighlightedWires) {
|
||||
Outliner.Outline(address, WireTracerColors.primaryNormal);
|
||||
}
|
||||
|
||||
foreach (WireAddress address in clusterDetails.HighlightedOutputWires) {
|
||||
Outliner.Outline(address, WireTracerColors.primaryOutput);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void RemoveHighLighting() {
|
||||
if (_clusters == null) return;
|
||||
foreach (ClusterDetails cluster in _clusters) {
|
||||
UnhighlightCluster(cluster);
|
||||
}
|
||||
}
|
||||
|
||||
private static void UnhighlightCluster(ClusterDetails cluster) {
|
||||
foreach (PegAddress address in cluster.Pegs) {
|
||||
Outliner.RemoveOutline(address);
|
||||
}
|
||||
|
||||
foreach (ComponentAddress address in cluster.ConnectingComponents) {
|
||||
Outliner.RemoveOutline(address);
|
||||
}
|
||||
|
||||
foreach (ComponentAddress address in cluster.LinkingComponents) {
|
||||
Outliner.RemoveOutline(address);
|
||||
}
|
||||
|
||||
foreach (WireAddress address in cluster.HighlightedWires) {
|
||||
Outliner.RemoveOutline(address);
|
||||
}
|
||||
|
||||
foreach (WireAddress address in cluster.HighlightedOutputWires) {
|
||||
Outliner.RemoveOutline(address);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
using JimmysUnityUtilities;
|
||||
using LogicWorld.Outlines;
|
||||
|
||||
namespace CriticalPathAnalyzer.client.tool
|
||||
{
|
||||
public static class WireTracerColors
|
||||
{
|
||||
//All clusters:
|
||||
//Linking color is the intersection between clusters, it does not make sense to have this once per cluster type.
|
||||
// The only change that could be done is to give linking separators between two non-primary clusters a different color.
|
||||
// That is currently not supported nor detected.
|
||||
public static readonly OutlineData linking = new OutlineData(new Color24(200, 200, 200));
|
||||
|
||||
//Primary cluster:
|
||||
public static readonly OutlineData primaryNormal = new OutlineData(new Color24( 50, 255, 50));
|
||||
public static readonly OutlineData primaryConnected = new OutlineData(new Color24( 20, 150, 20));
|
||||
public static readonly OutlineData primaryOutput = new OutlineData(new Color24( 50, 50, 255));
|
||||
|
||||
//Sourcing cluster:
|
||||
public static readonly OutlineData sourcingNormal = new OutlineData(new Color24(255, 50, 255));
|
||||
public static readonly OutlineData sourcingConnected = new OutlineData(new Color24(150, 20, 150));
|
||||
public static readonly OutlineData sourcingOutput = new OutlineData(new Color24( 80, 0, 255));
|
||||
|
||||
//Connected cluster:
|
||||
public static readonly OutlineData connectedNormal = new OutlineData(new Color24(255, 255, 50));
|
||||
public static readonly OutlineData connectedConnected = new OutlineData(new Color24(150, 150, 20));
|
||||
public static readonly OutlineData connectedOutput = new OutlineData(new Color24( 80, 80, 255));
|
||||
|
||||
//Draining cluster:
|
||||
public static readonly OutlineData drainingNormal = new OutlineData(new Color24( 50, 255, 255));
|
||||
public static readonly OutlineData drainingConnected = new OutlineData(new Color24( 20, 150, 150));
|
||||
public static readonly OutlineData drainingOutput = new OutlineData(new Color24( 0, 50, 150));
|
||||
}
|
||||
}
|
@ -0,0 +1,238 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using CriticalPathAnalyzer.Shared.Packets.S2C;
|
||||
using EccsLogicWorldAPI.Server;
|
||||
using EccsLogicWorldAPI.Shared.AccessHelper;
|
||||
using LogicAPI.Data;
|
||||
using LogicAPI.Services;
|
||||
using LogicWorld.Server.Circuitry;
|
||||
|
||||
namespace CriticalPathAnalyzer.Server {
|
||||
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>> GetLeaders;
|
||||
|
||||
private static readonly Func<ClusterLinker, List<ClusterLinker>> GetFollowers;
|
||||
|
||||
// Services needed to lookup wires/pegs:
|
||||
private static readonly ICircuitryManager Circuits;
|
||||
private static readonly IWorldData World;
|
||||
|
||||
static ServerPathTracer() {
|
||||
GetCluster = Delegator.createPropertyGetter<InputPeg, Cluster>(
|
||||
Properties.getPrivate(typeof(InputPeg), "Cluster")
|
||||
);
|
||||
GetLinker = Delegator.createFieldGetter<Cluster, ClusterLinker>(
|
||||
Fields.getPrivate(typeof(Cluster), "Linker")
|
||||
);
|
||||
GetLeaders = Delegator.createFieldGetter<ClusterLinker, List<ClusterLinker>>(
|
||||
Fields.getPrivate(typeof(ClusterLinker), "LinkedLeaders")
|
||||
);
|
||||
GetFollowers = Delegator.createFieldGetter<ClusterLinker, List<ClusterLinker>>(
|
||||
Fields.getPrivate(typeof(ClusterLinker), "LinkedFollowers")
|
||||
);
|
||||
|
||||
Circuits = ServiceGetter.getService<ICircuitryManager>();
|
||||
World = ServiceGetter.getService<IWorldData>();
|
||||
}
|
||||
|
||||
public static bool TracePath(
|
||||
Guid requestGuid,
|
||||
PegAddress start,
|
||||
PegAddress end,
|
||||
out AnalyzePathResponse response
|
||||
) {
|
||||
response = null;
|
||||
|
||||
CriticalPathAnalyzerServer.LoggerInstance.Info("Trace start");
|
||||
|
||||
// Validate, that the peg is actually existing:
|
||||
if (!PegExists(start) || !PegExists(end)) {
|
||||
CriticalPathAnalyzerServer.LoggerInstance.Error("Peg not found");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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.
|
||||
if (!CollectMainClusters(start, out HashSet<Cluster> primaryClusters)) {
|
||||
return false; // Whoops, cannot collect the primary clusters, probably probing an output peg.
|
||||
}
|
||||
|
||||
// Collect clusters that get powered by the original cluster or will power it.
|
||||
var collectedSources = new HashSet<Cluster>();
|
||||
var collectedDrains = new HashSet<Cluster>();
|
||||
foreach (Cluster cluster in primaryClusters) {
|
||||
CollectClusters(cluster, collectedSources, GetLeaders);
|
||||
CollectClusters(cluster, collectedDrains, GetFollowers);
|
||||
}
|
||||
|
||||
foreach (Cluster cluster in primaryClusters) {
|
||||
collectedSources.Remove(cluster);
|
||||
collectedDrains.Remove(cluster);
|
||||
}
|
||||
|
||||
// Collect and filter clusters that are both source and drain:
|
||||
var collectedEquals = new HashSet<Cluster>();
|
||||
foreach (Cluster collectedSource in collectedSources) {
|
||||
if (collectedDrains.Remove(collectedSource)) {
|
||||
collectedEquals.Add(collectedSource);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Cluster collectedEqual in collectedEquals) {
|
||||
collectedSources.Remove(collectedEqual);
|
||||
}
|
||||
|
||||
//Collect information about each cluster:
|
||||
response = new AnalyzePathResponse() {
|
||||
RequestGuid = requestGuid,
|
||||
SelectedClusters = new List<ClusterDetails>(),
|
||||
};
|
||||
foreach (Cluster cluster in primaryClusters) {
|
||||
response.SelectedClusters.Add(CollectClusterInformation(cluster));
|
||||
}
|
||||
|
||||
// response.sourcingClusters = new List<ClusterDetails>();
|
||||
// foreach (var cluster in collectedSources) {
|
||||
// response.sourcingClusters.Add(CollectClusterInformation(cluster));
|
||||
// }
|
||||
//
|
||||
// response.connectedClusters = new List<ClusterDetails>();
|
||||
// foreach (var cluster in collectedEquals) {
|
||||
// response.connectedClusters.Add(CollectClusterInformation(cluster));
|
||||
// }
|
||||
//
|
||||
// response.drainingClusters = new List<ClusterDetails>();
|
||||
// foreach (var cluster in collectedDrains) {
|
||||
// response.drainingClusters.Add(CollectClusterInformation(cluster));
|
||||
// }
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private static bool PegExists(PegAddress address) {
|
||||
IComponentInWorld component = World.Lookup(address.ComponentAddress);
|
||||
if (component == null) {
|
||||
return false; // Component of the peg does not exist in world.
|
||||
}
|
||||
|
||||
int pegAmount = address.IsInputAddress() ? component.Data.InputCount : component.Data.OutputCount;
|
||||
//If false: Component does not have this peg, as the peg index is bigger than the actual components input/output count.
|
||||
return pegAmount >= address.PegIndex;
|
||||
}
|
||||
|
||||
private static Cluster GetClusterAt(InputAddress peg) {
|
||||
InputPeg originPeg = Circuits.LookupInput(peg);
|
||||
if (originPeg == null) {
|
||||
throw new Exception(
|
||||
"Tried to lookup cluster on input peg, but the peg was not present in the circuit model! This should never happen, as the peg is present in the world.");
|
||||
}
|
||||
|
||||
Cluster cluster = GetCluster(originPeg);
|
||||
if (cluster == null) {
|
||||
throw new Exception(
|
||||
"Tried to lookup cluster on input peg, but the cluster was 'null', this should never happen! As the peg is present in the world.");
|
||||
}
|
||||
|
||||
return cluster;
|
||||
}
|
||||
|
||||
private static bool GetLinkerAt(Cluster cluster, out ClusterLinker linker) {
|
||||
linker = GetLinker(cluster);
|
||||
return linker != null;
|
||||
}
|
||||
|
||||
private static bool CollectMainClusters(PegAddress pegAddress, out HashSet<Cluster> primaryClusters) {
|
||||
primaryClusters = new HashSet<Cluster>();
|
||||
if (pegAddress.IsInputAddress(out InputAddress inputAddress)) {
|
||||
primaryClusters.Add(GetClusterAt(inputAddress));
|
||||
} else {
|
||||
HashSet<WireAddress> wires = World.LookupPegWires(pegAddress);
|
||||
if (wires == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (WireAddress wireAddress in wires) {
|
||||
Wire wire = World.Lookup(wireAddress);
|
||||
if (wire == null) {
|
||||
throw new Exception(
|
||||
"Tried to lookup wire given its address, but the world did not contain it. World must be corrupted.");
|
||||
}
|
||||
|
||||
PegAddress otherSide = wire.Point1 == pegAddress ? wire.Point2 : wire.Point1;
|
||||
if (!otherSide.IsInputAddress(out var otherSideInputAddress)) {
|
||||
continue; //Not supported, this wire would be invalid anyway.
|
||||
}
|
||||
|
||||
primaryClusters.Add(GetClusterAt(otherSideInputAddress));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void CollectClusters(
|
||||
Cluster startingPoint,
|
||||
HashSet<Cluster> collectedClusters,
|
||||
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);
|
||||
// While the starting cluster is no source, the algorithm needs to skip it when encountered.
|
||||
collectedClusters.Add(startingPoint);
|
||||
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.
|
||||
if (collectedClusters.Add(clusterOfLinkedLinker)) {
|
||||
// Element was not yet present in the array, so keep looking into it!
|
||||
clustersToProcess.Enqueue(linkedLinker);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static ClusterDetails CollectClusterInformation(Cluster cluster) {
|
||||
var details = new ClusterDetails {
|
||||
Pegs = new List<PegAddress>(),
|
||||
ConnectingComponents = new List<ComponentAddress>(),
|
||||
LinkingComponents = new List<ComponentAddress>(),
|
||||
};
|
||||
|
||||
// Two lists are never null, according to how it is created and used:
|
||||
IReadOnlyList<InputPeg> inputPegs = cluster.ConnectedInputs;
|
||||
IReadOnlyList<OutputPeg> outputPegs = cluster.ConnectedOutputs;
|
||||
|
||||
foreach (InputPeg peg in inputPegs) {
|
||||
details.Pegs.Add(peg.Address);
|
||||
if (peg.SecretLinks != null && peg.SecretLinks.Any()) {
|
||||
// Highlight this component somehow.
|
||||
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())
|
||||
) {
|
||||
details.LinkingComponents.Add(peg.Address.ComponentAddress);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (OutputPeg peg in outputPegs) {
|
||||
details.Pegs.Add(peg.Address);
|
||||
}
|
||||
|
||||
return details;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in new issue