Get cluster info

master
D4VID 7 months ago
parent bc74dcd6ee
commit 5b542dbcc8

@ -14,6 +14,9 @@
<Reference Include="FancyInput">
<HintPath>..\LogicWorld\Logic_World_Data\Managed\FancyInput.dll</HintPath>
</Reference>
<Reference Include="JimmysUnityUtilities">
<HintPath>..\LogicWorld\Logic_World_Data\Managed\JimmysUnityUtilities.dll</HintPath>
</Reference>
<Reference Include="LogicAPI">
<HintPath>..\LogicWorld\Logic_World_Data\Managed\LogicAPI.dll</HintPath>
</Reference>

@ -33,7 +33,21 @@ namespace CriticalPathAnalyzer.Client {
}
public static void OnAnalyzePathResponse(AnalyzePathResponse response) {
LoggerInstance.Info($"Got response from server: {response.Message}");
LoggerInstance.Info($"Got response from server");
PathHighLighter.HighlightWires(response);
// if(!CurrentRequestID.HasValue || response.requestGuid != currentRequestID.Value)
// {
// //Not matching Guid, old or wrong request, discard.
// return;
// }
// currentRequestID = null; //Received response, clear GUID.
//
// //Clear up all data immediately:
// if(currentTracer != null)
// {
// currentTracer.stop();
// currentTracer = null;
// }
}
}
}

@ -34,6 +34,7 @@ namespace CriticalPathAnalyzer.Client {
public override void OnExit() {
CriticalPathAnalyzerClient.LoggerInstance.Info("CPA exit");
PathHighLighter.RemoveHighLighting();
}
}
}

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using CriticalPathAnalyzer.Shared.Packets.C2S;
using LogicAPI.Data;
using LogicLog;
@ -29,12 +28,12 @@ namespace CriticalPathAnalyzer.Client.Tool {
// Resolve hit target:
if (hitInfo.HitPeg) {
CriticalPathAnalyzerClient.LoggerInstance.Info("Hit peg");
_logger.Info("Hit peg");
return _startPegAddress = hitInfo.pAddress;
}
if (hitInfo.HitWire) {
CriticalPathAnalyzerClient.LoggerInstance.Info("Hit wire");
_logger.Info("Hit wire");
WireAddress wireAddress = hitInfo.wAddress;
Wire wire = Instances.MainWorld.Data.Lookup(wireAddress);
// Assume that wire is never null, as we did just ray-casted it.
@ -45,7 +44,7 @@ namespace CriticalPathAnalyzer.Client.Tool {
}
public static void SelectPathStart() {
CriticalPathAnalyzerClient.LoggerInstance.Info("Analyze Path Start");
_logger.Info("Analyze Path Start");
PegAddress? pegAddress = RayCastPeg();
if (pegAddress != null) {
_startPegAddress = pegAddress.Value;
@ -56,7 +55,7 @@ namespace CriticalPathAnalyzer.Client.Tool {
}
public static void SelectPathEnd() {
CriticalPathAnalyzerClient.LoggerInstance.Info("Analyze Path End");
_logger.Info("Analyze Path End");
PegAddress? pegAddress = RayCastPeg();
if (pegAddress != null) {
_endPegAddress = pegAddress.Value;
@ -67,35 +66,16 @@ namespace CriticalPathAnalyzer.Client.Tool {
}
private static void CalculateCriticalPath() {
if (_startPegAddress == null || _endPegAddress == null) {
_logger.Error("Invalid pegs");
return;
}
Instances.SendData.Send(new AnalyzePathRequest {
RequestGuid = Guid.NewGuid(),
StartPegAddress = _startPegAddress.Value,
EndPegAddress = _endPegAddress.Value,
});
HashSet<WireAddress> wires = Instances.MainWorld.Data.LookupPegWires(_startPegAddress.Value);
_logger.Info($"Start peg has {wires.Count} wires");
ComponentType andGate = Instances.MainWorld.ComponentTypes.GetComponentType("MHG.AndGate");
ComponentType xorGate = Instances.MainWorld.ComponentTypes.GetComponentType("MHG.XorGate");
foreach (WireAddress wireAddress in wires) {
Wire wire = Instances.MainWorld.Data.Lookup(wireAddress);
PegAddress nextPeg = wire.Point1 == _startPegAddress ? wire.Point2 : wire.Point1;
if (nextPeg.PegType != PegType.Input) {
// ignore
continue;
}
IComponentInWorld component = Instances.MainWorld.Data.Lookup(nextPeg.ComponentAddress);
ComponentType type = component.Data.Type;
if (type == andGate) {
_logger.Info($"Connected to AND gate");
} else if (type == xorGate) {
_logger.Info($"Connected to XOR gate");
} else {
_logger.Info($"Connected to unknown gate");
}
}
}
}
}

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

@ -16,6 +16,7 @@ using LogicWorld.Server;
using LogicWorld.SharedCode.Networking;
namespace CriticalPathAnalyzer.Server {
// ReSharper disable once ClassNeverInstantiated.Global
public class CriticalPathAnalyzerServer : ServerMod, IClientVerifier {
private const string ModId = "CriticalPathAnalyzer";
private const string ModVersion = "0.0.2";
@ -57,12 +58,13 @@ namespace CriticalPathAnalyzer.Server {
}
}
public void AnalyzePath(Connection sender, Guid packetRequestGuid, PegAddress start, PegAddress end) {
public void AnalyzePath(Connection sender, Guid requestGuid, PegAddress start, PegAddress end) {
LoggerInstance.Info("Got AnalyzePath request");
AnalyzePathResponse response = new AnalyzePathResponse() {
RequestGuid = packetRequestGuid,
Message = "Lmao Yeet",
};
if (!ServerPathTracer.TracePath(requestGuid, start, end, out AnalyzePathResponse response)) {
Logger.Error("Failed to trace path");
return;
}
_networkServer.Send(sender, response);
}
}

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

@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using LogicAPI.Data;
using LogicAPI.Networking.Packets;
using MessagePack;
@ -6,6 +8,25 @@ namespace CriticalPathAnalyzer.Shared.Packets.S2C {
[MessagePackObject]
public class AnalyzePathResponse : Packet {
[Key(0)] public Guid RequestGuid;
[Key(1)] public string Message;
[Key(1)]
public List<ClusterDetails> SelectedClusters;
}
[MessagePackObject]
public sealed class ClusterDetails
{
[Key(0)]
public List<PegAddress> Pegs;
[Key(1)]
public List<ComponentAddress> ConnectingComponents;
[Key(2)]
public List<ComponentAddress> LinkingComponents;
// The following two entries are used on the client to temporary store wires until their outline is being removed again.
// This information is just not collected on the server, hence the client collects them.
[IgnoreMember]
public List<WireAddress> HighlightedWires;
[IgnoreMember]
public List<WireAddress> HighlightedOutputWires;
}
}
Loading…
Cancel
Save