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