feat: update routing methods

Routing methods will now take a source port and destination port. This should solve the issue where a device could have multiple input ports defined in tielines and allow Essentials routing to find a path correctly.
This commit is contained in:
Andrew Welker
2024-06-19 14:09:59 -05:00
parent 3a56e47c48
commit 25ebcdfb5d
3 changed files with 177 additions and 130 deletions

View File

@@ -14,16 +14,28 @@ namespace PepperDash.Essentials.Core
public static class Extensions public static class Extensions
{ {
private static readonly Dictionary<string, RouteRequest> RouteRequests = new Dictionary<string, RouteRequest>(); private static readonly Dictionary<string, RouteRequest> RouteRequests = new Dictionary<string, RouteRequest>();
/// <summary> /// <summary>
/// Gets any existing RouteDescriptor for a destination, clears it using ReleaseRoute /// Gets any existing RouteDescriptor for a destination, clears it using ReleaseRoute
/// and then attempts a new Route and if sucessful, stores that RouteDescriptor /// and then attempts a new Route and if sucessful, stores that RouteDescriptor
/// in RouteDescriptorCollection.DefaultCollection /// in RouteDescriptorCollection.DefaultCollection
/// </summary> /// </summary>
public static void ReleaseAndMakeRoute(this IRoutingSink destination, IRoutingOutputs source, eRoutingSignalType signalType) public static void ReleaseAndMakeRoute(this IRoutingInputs destination, IRoutingOutputs source, eRoutingSignalType signalType, string destinationPortKey = "", string sourcePortKey = "")
{
var inputPort = string.IsNullOrEmpty(destinationPortKey) ? null : destination.InputPorts.FirstOrDefault(p => p.Key == destinationPortKey);
var outputPort = string.IsNullOrEmpty(sourcePortKey) ? null : source.OutputPorts.FirstOrDefault(p => p.Key == sourcePortKey);
ReleaseAndMakeRoute(destination, source, signalType, inputPort, outputPort);
}
private static void ReleaseAndMakeRoute(IRoutingInputs destination, IRoutingOutputs source, eRoutingSignalType signalType, RoutingInputPort destinationPort = null, RoutingOutputPort sourcePort = null)
{
var routeRequest = new RouteRequest
{ {
var routeRequest = new RouteRequest {
Destination = destination, Destination = destination,
DestinationPort = destinationPort,
Source = source, Source = source,
SourcePort = sourcePort,
SignalType = signalType SignalType = signalType
}; };
@@ -73,7 +85,7 @@ namespace PepperDash.Essentials.Core
if (request.Source == null) if (request.Source == null)
return; return;
var newRoute = request.Destination.GetRouteToSource(request.Source, request.SignalType); var newRoute = request.Destination.GetRouteToSource(request.Source, request.SignalType, request.DestinationPort, request.SourcePort);
if (newRoute == null) if (newRoute == null)
return; return;
@@ -90,7 +102,7 @@ namespace PepperDash.Essentials.Core
/// RouteDescriptorCollection.DefaultCollection /// RouteDescriptorCollection.DefaultCollection
/// </summary> /// </summary>
/// <param name="destination"></param> /// <param name="destination"></param>
public static void ReleaseRoute(this IRoutingSink destination) public static void ReleaseRoute(this IRoutingInputs destination)
{ {
if (RouteRequests.TryGetValue(destination.Key, out RouteRequest existingRequest) && destination is IWarmingCooling) if (RouteRequests.TryGetValue(destination.Key, out RouteRequest existingRequest) && destination is IWarmingCooling)
@@ -117,7 +129,7 @@ namespace PepperDash.Essentials.Core
/// of an audio/video route are discovered a route descriptor is returned. If no route is /// of an audio/video route are discovered a route descriptor is returned. If no route is
/// discovered, then null is returned /// discovered, then null is returned
/// </summary> /// </summary>
public static RouteDescriptor GetRouteToSource(this IRoutingSink destination, IRoutingOutputs source, eRoutingSignalType signalType) public static RouteDescriptor GetRouteToSource(this IRoutingInputs destination, IRoutingOutputs source, eRoutingSignalType signalType, RoutingInputPort destinationPort, RoutingOutputPort sourcePort)
{ {
var routeDescriptor = new RouteDescriptor(source, destination, signalType); var routeDescriptor = new RouteDescriptor(source, destination, signalType);
@@ -126,7 +138,7 @@ namespace PepperDash.Essentials.Core
{ {
Debug.LogMessage(LogEventLevel.Debug, "Attempting to build source route from {0}", null, source.Key); Debug.LogMessage(LogEventLevel.Debug, "Attempting to build source route from {0}", null, source.Key);
if (!destination.GetRouteToSource(source, null, null, signalType, 0, routeDescriptor)) if (!destination.GetRouteToSource(source, sourcePort, null, signalType, 0, routeDescriptor, destinationPort))
routeDescriptor = null; routeDescriptor = null;
return routeDescriptor; return routeDescriptor;
@@ -135,12 +147,12 @@ namespace PepperDash.Essentials.Core
Debug.LogMessage(LogEventLevel.Debug, "Attempting to build audio and video routes from {0}", destination, source.Key); Debug.LogMessage(LogEventLevel.Debug, "Attempting to build audio and video routes from {0}", destination, source.Key);
var audioSuccess = destination.GetRouteToSource(source, null, null, eRoutingSignalType.Audio, 0, routeDescriptor); var audioSuccess = destination.GetRouteToSource(source, sourcePort, null, eRoutingSignalType.Audio, 0, routeDescriptor, destinationPort);
if (!audioSuccess) if (!audioSuccess)
Debug.LogMessage(LogEventLevel.Debug, "Cannot find audio route to {0}", destination, source.Key); Debug.LogMessage(LogEventLevel.Debug, "Cannot find audio route to {0}", destination, source.Key);
var videoSuccess = destination.GetRouteToSource(source, null, null, eRoutingSignalType.Video, 0, routeDescriptor); var videoSuccess = destination.GetRouteToSource(source, sourcePort, null, eRoutingSignalType.Video, 0, routeDescriptor, destinationPort);
if (!videoSuccess) if (!videoSuccess)
Debug.LogMessage(LogEventLevel.Debug, "Cannot find video route to {0}", destination, source.Key); Debug.LogMessage(LogEventLevel.Debug, "Cannot find video route to {0}", destination, source.Key);
@@ -159,15 +171,15 @@ namespace PepperDash.Essentials.Core
/// </summary> /// </summary>
/// <param name="destination"></param> /// <param name="destination"></param>
/// <param name="source"></param> /// <param name="source"></param>
/// <param name="outputPortToUse">The RoutingOutputPort whose link is being checked for a route</param> /// <param name="destinationPort">The RoutingOutputPort whose link is being checked for a route</param>
/// <param name="alreadyCheckedDevices">Prevents Devices from being twice-checked</param> /// <param name="alreadyCheckedDevices">Prevents Devices from being twice-checked</param>
/// <param name="signalType">This recursive function should not be called with AudioVideo</param> /// <param name="signalType">This recursive function should not be called with AudioVideo</param>
/// <param name="cycle">Just an informational counter</param> /// <param name="cycle">Just an informational counter</param>
/// <param name="routeTable">The RouteDescriptor being populated as the route is discovered</param> /// <param name="routeTable">The RouteDescriptor being populated as the route is discovered</param>
/// <returns>true if source is hit</returns> /// <returns>true if source is hit</returns>
static bool GetRouteToSource(this IRoutingInputs destination, IRoutingOutputs source, private static bool GetRouteToSource(this IRoutingInputs destination, IRoutingOutputs source,
RoutingOutputPort outputPortToUse, List<IRoutingInputsOutputs> alreadyCheckedDevices, RoutingOutputPort sourcePort, List<IRoutingInputsOutputs> alreadyCheckedDevices,
eRoutingSignalType signalType, int cycle, RouteDescriptor routeTable) eRoutingSignalType signalType, int cycle, RouteDescriptor routeTable, RoutingInputPort destinationPort)
{ {
cycle++; cycle++;
@@ -175,13 +187,41 @@ namespace PepperDash.Essentials.Core
RoutingInputPort goodInputPort = null; RoutingInputPort goodInputPort = null;
var destinationTieLines = TieLineCollection.Default.Where(t => IEnumerable<TieLine> destinationTieLines;
t.DestinationPort.ParentDevice == destination && (t.Type == signalType || t.Type.HasFlag(eRoutingSignalType.AudioVideo))); TieLine directTie = null;
if (destinationPort == null)
{
destinationTieLines = TieLineCollection.Default.Where(t =>
t.DestinationPort.ParentDevice.Key == destination.Key && (t.Type == signalType || t.Type.HasFlag(eRoutingSignalType.AudioVideo)));
}
else
{
destinationTieLines = TieLineCollection.Default.Where(t => t.DestinationPort.ParentDevice.Key == destination.Key && (t.Type == signalType || t.Type.HasFlag(eRoutingSignalType.AudioVideo)));
}
// find the TieLine without a port
if (destinationPort == null && sourcePort == null)
{
directTie = destinationTieLines.FirstOrDefault(t => t.DestinationPort.ParentDevice.Key == destination.Key && t.SourcePort.ParentDevice.Key == source.Key);
}
// find a tieLine to a specific destination port without a specific source port
else if (destinationPort != null && sourcePort == null)
{
directTie = destinationTieLines.FirstOrDefault(t => t.DestinationPort.Key == destinationPort.Key && t.SourcePort.ParentDevice.Key == source.Key);
}
// find a tieline to a specific source port without a specific destination port
else if (destinationPort == null & sourcePort != null)
{
directTie = destinationTieLines.FirstOrDefault(t => t.DestinationPort.ParentDevice.Key == destination.Key && t.SourcePort.Key == sourcePort.Key);
}
// find a tieline to a specific source port and destination port
else if (destinationPort != null && sourcePort != null)
{
directTie = destinationTieLines.FirstOrDefault(t => t.DestinationPort.Key == destinationPort.Key && t.SourcePort.Key == sourcePort.Key);
}
// find a direct tie
var directTie = destinationTieLines.FirstOrDefault(
t => t.DestinationPort.ParentDevice == destination
&& t.SourcePort.ParentDevice == source);
if (directTie != null) // Found a tie directly to the source if (directTie != null) // Found a tie directly to the source
{ {
goodInputPort = directTie.DestinationPort; goodInputPort = directTie.DestinationPort;
@@ -192,33 +232,37 @@ namespace PepperDash.Essentials.Core
// No direct tie? Run back out on the inputs' attached devices... // No direct tie? Run back out on the inputs' attached devices...
// Only the ones that are routing devices // Only the ones that are routing devices
var attachedMidpoints = destinationTieLines.Where(t => t.SourcePort.ParentDevice is IRoutingInputsOutputs); var midpointTieLines = destinationTieLines.Where(t => t.SourcePort.ParentDevice is IRoutingInputsOutputs);
//Create a list for tracking already checked devices to avoid loops, if it doesn't already exist from previous iteration //Create a list for tracking already checked devices to avoid loops, if it doesn't already exist from previous iteration
if (alreadyCheckedDevices == null) if (alreadyCheckedDevices == null)
alreadyCheckedDevices = new List<IRoutingInputsOutputs>(); alreadyCheckedDevices = new List<IRoutingInputsOutputs>();
alreadyCheckedDevices.Add(destination as IRoutingInputsOutputs); alreadyCheckedDevices.Add(destination as IRoutingInputsOutputs);
foreach (var inputTieToTry in attachedMidpoints) foreach (var tieLine in midpointTieLines)
{ {
var upstreamDeviceOutputPort = inputTieToTry.SourcePort; var midpointDevice = tieLine.SourcePort.ParentDevice as IRoutingInputsOutputs;
var upstreamRoutingDevice = upstreamDeviceOutputPort.ParentDevice as IRoutingInputsOutputs;
Debug.LogMessage(LogEventLevel.Verbose, "Trying to find route on {0}", destination, upstreamRoutingDevice.Key);
// Check if this previous device has already been walked // Check if this previous device has already been walked
if (alreadyCheckedDevices.Contains(upstreamRoutingDevice)) if (alreadyCheckedDevices.Contains(midpointDevice))
{ {
Debug.LogMessage(LogEventLevel.Verbose, "Skipping input {0} on {1}, this was already checked", destination, upstreamRoutingDevice.Key, destination.Key); Debug.LogMessage(LogEventLevel.Verbose, "Skipping input {0} on {1}, this was already checked", destination, midpointDevice.Key, destination.Key);
continue; continue;
} }
var midpointOutputPort = sourcePort ?? tieLine.SourcePort;
Debug.LogMessage(LogEventLevel.Verbose, "Trying to find route on {0}", destination, midpointDevice.Key);
// haven't seen this device yet. Do it. Pass the output port to the next // haven't seen this device yet. Do it. Pass the output port to the next
// level to enable switching on success // level to enable switching on success
var upstreamRoutingSuccess = upstreamRoutingDevice.GetRouteToSource(source, upstreamDeviceOutputPort, var upstreamRoutingSuccess = midpointDevice.GetRouteToSource(source, midpointOutputPort,
alreadyCheckedDevices, signalType, cycle, routeTable); alreadyCheckedDevices, signalType, cycle, routeTable, null);
if (upstreamRoutingSuccess) if (upstreamRoutingSuccess)
{ {
Debug.LogMessage(LogEventLevel.Verbose, "Upstream device route found", destination); Debug.LogMessage(LogEventLevel.Verbose, "Upstream device route found", destination);
goodInputPort = inputTieToTry.DestinationPort; goodInputPort = tieLine.DestinationPort;
break; // Stop looping the inputs in this cycle break; // Stop looping the inputs in this cycle
} }
} }
@@ -233,14 +277,14 @@ namespace PepperDash.Essentials.Core
// we have a route on corresponding inputPort. *** Do the route *** // we have a route on corresponding inputPort. *** Do the route ***
if (outputPortToUse == null) if (sourcePort == null)
{ {
// it's a sink device // it's a sink device
routeTable.Routes.Add(new RouteSwitchDescriptor(goodInputPort)); routeTable.Routes.Add(new RouteSwitchDescriptor(goodInputPort));
} }
else if (destination is IRouting) else if (destination is IRouting)
{ {
routeTable.Routes.Add(new RouteSwitchDescriptor(outputPortToUse, goodInputPort)); routeTable.Routes.Add(new RouteSwitchDescriptor(sourcePort, goodInputPort));
} }
else // device is merely IRoutingInputOutputs else // device is merely IRoutingInputOutputs
Debug.LogMessage(LogEventLevel.Verbose, "No routing. Passthrough device", destination); Debug.LogMessage(LogEventLevel.Verbose, "No routing. Passthrough device", destination);

View File

@@ -3,7 +3,7 @@
/// <summary> /// <summary>
/// Defines an IRoutingOutputs devices as being a source - the start of the chain /// Defines an IRoutingOutputs devices as being a source - the start of the chain
/// </summary> /// </summary>
public interface IRoutingSource public interface IRoutingSource : IRoutingOutputs
{ {
} }
} }

View File

@@ -2,19 +2,22 @@
{ {
public class RouteRequest public class RouteRequest
{ {
public IRoutingSink Destination {get; set;} public RoutingInputPort DestinationPort { get; set; }
public IRoutingOutputs Source {get; set;}
public eRoutingSignalType SignalType {get; set;} public RoutingOutputPort SourcePort { get; set; }
public IRoutingInputs Destination { get; set; }
public IRoutingOutputs Source { get; set; }
public eRoutingSignalType SignalType { get; set; }
public void HandleCooldown(object sender, FeedbackEventArgs args) public void HandleCooldown(object sender, FeedbackEventArgs args)
{ {
var coolingDevice = sender as IWarmingCooling; var coolingDevice = sender as IWarmingCooling;
if(args.BoolValue == false) if (args.BoolValue == false)
{ {
Destination.ReleaseAndMakeRoute(Source, SignalType); Destination.ReleaseAndMakeRoute(Source, SignalType);
if(sender == null) return; if (sender == null) return;
coolingDevice.IsCoolingDownFeedback.OutputChange -= HandleCooldown; coolingDevice.IsCoolingDownFeedback.OutputChange -= HandleCooldown;
} }