From 25ebcdfb5d0b8e170ab837c5f3f75c5fb102f80b Mon Sep 17 00:00:00 2001 From: Andrew Welker Date: Wed, 19 Jun 2024 14:09:59 -0500 Subject: [PATCH] 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. --- .../Routing/Extensions.cs | 284 ++++++++++-------- .../Routing/IRoutingSource.cs | 6 +- .../Routing/RouteRequest.cs | 17 +- 3 files changed, 177 insertions(+), 130 deletions(-) diff --git a/src/PepperDash.Essentials.Core/Routing/Extensions.cs b/src/PepperDash.Essentials.Core/Routing/Extensions.cs index 03c59bf0..bf476531 100644 --- a/src/PepperDash.Essentials.Core/Routing/Extensions.cs +++ b/src/PepperDash.Essentials.Core/Routing/Extensions.cs @@ -12,20 +12,32 @@ namespace PepperDash.Essentials.Core /// on those destinations. /// public static class Extensions - { + { private static readonly Dictionary RouteRequests = new Dictionary(); - /// - /// Gets any existing RouteDescriptor for a destination, clears it using ReleaseRoute - /// and then attempts a new Route and if sucessful, stores that RouteDescriptor - /// in RouteDescriptorCollection.DefaultCollection - /// - public static void ReleaseAndMakeRoute(this IRoutingSink destination, IRoutingOutputs source, eRoutingSignalType signalType) - { - var routeRequest = new RouteRequest { - Destination = destination, - Source = source, - SignalType = signalType - }; + + /// + /// Gets any existing RouteDescriptor for a destination, clears it using ReleaseRoute + /// and then attempts a new Route and if sucessful, stores that RouteDescriptor + /// in RouteDescriptorCollection.DefaultCollection + /// + 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 + { + Destination = destination, + DestinationPort = destinationPort, + Source = source, + SourcePort = sourcePort, + SignalType = signalType + }; var coolingDevice = destination as IWarmingCooling; @@ -65,15 +77,15 @@ namespace PepperDash.Essentials.Core destination.ReleaseRoute(); - RunRouteRequest(routeRequest); - } + RunRouteRequest(routeRequest); + } private static void RunRouteRequest(RouteRequest request) { if (request.Source == null) 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) return; @@ -85,13 +97,13 @@ namespace PepperDash.Essentials.Core newRoute.ExecuteRoutes(); } - /// - /// Will release the existing route on the destination, if it is found in - /// RouteDescriptorCollection.DefaultCollection - /// - /// - public static void ReleaseRoute(this IRoutingSink destination) - { + /// + /// Will release the existing route on the destination, if it is found in + /// RouteDescriptorCollection.DefaultCollection + /// + /// + public static void ReleaseRoute(this IRoutingInputs destination) + { if (RouteRequests.TryGetValue(destination.Key, out RouteRequest existingRequest) && destination is IWarmingCooling) { @@ -102,150 +114,182 @@ namespace PepperDash.Essentials.Core RouteRequests.Remove(destination.Key); - var current = RouteDescriptorCollection.DefaultCollection.RemoveRouteDescriptor(destination); - if (current != null) - { - Debug.LogMessage(LogEventLevel.Debug, "Releasing current route: {0}", destination, current.Source.Key); - current.ReleaseRoutes(); - } - } + var current = RouteDescriptorCollection.DefaultCollection.RemoveRouteDescriptor(destination); + if (current != null) + { + Debug.LogMessage(LogEventLevel.Debug, "Releasing current route: {0}", destination, current.Source.Key); + current.ReleaseRoutes(); + } + } - /// - /// Builds a RouteDescriptor that contains the steps necessary to make a route between devices. - /// Routes of type AudioVideo will be built as two separate routes, audio and video. If - /// a route is discovered, a new RouteDescriptor is returned. If one or both parts - /// of an audio/video route are discovered a route descriptor is returned. If no route is - /// discovered, then null is returned - /// - public static RouteDescriptor GetRouteToSource(this IRoutingSink destination, IRoutingOutputs source, eRoutingSignalType signalType) - { - var routeDescriptor = new RouteDescriptor(source, destination, signalType); + /// + /// Builds a RouteDescriptor that contains the steps necessary to make a route between devices. + /// Routes of type AudioVideo will be built as two separate routes, audio and video. If + /// a route is discovered, a new RouteDescriptor is returned. If one or both parts + /// of an audio/video route are discovered a route descriptor is returned. If no route is + /// discovered, then null is returned + /// + public static RouteDescriptor GetRouteToSource(this IRoutingInputs destination, IRoutingOutputs source, eRoutingSignalType signalType, RoutingInputPort destinationPort, RoutingOutputPort sourcePort) + { + var routeDescriptor = new RouteDescriptor(source, destination, signalType); - // if it's a single signal type, find the route - if (!signalType.HasFlag(eRoutingSignalType.AudioVideo)) - { - Debug.LogMessage(LogEventLevel.Debug, "Attempting to build source route from {0}", null, source.Key); + // if it's a single signal type, find the route + if (!signalType.HasFlag(eRoutingSignalType.AudioVideo)) + { + Debug.LogMessage(LogEventLevel.Debug, "Attempting to build source route from {0}", null, source.Key); - if (!destination.GetRouteToSource(source, null, null, signalType, 0, routeDescriptor)) - routeDescriptor = null; + if (!destination.GetRouteToSource(source, sourcePort, null, signalType, 0, routeDescriptor, destinationPort)) + routeDescriptor = null; return routeDescriptor; - } - // otherwise, audioVideo needs to be handled as two steps. - - Debug.LogMessage(LogEventLevel.Debug, "Attempting to build audio and video routes from {0}", destination, source.Key); + } + // otherwise, audioVideo needs to be handled as two steps. - var audioSuccess = destination.GetRouteToSource(source, null, null, eRoutingSignalType.Audio, 0, routeDescriptor); + Debug.LogMessage(LogEventLevel.Debug, "Attempting to build audio and video routes from {0}", destination, source.Key); - if (!audioSuccess) - Debug.LogMessage(LogEventLevel.Debug, "Cannot find audio route to {0}", destination, source.Key); + var audioSuccess = destination.GetRouteToSource(source, sourcePort, null, eRoutingSignalType.Audio, 0, routeDescriptor, destinationPort); - var videoSuccess = destination.GetRouteToSource(source, null, null, eRoutingSignalType.Video, 0, routeDescriptor); + if (!audioSuccess) + Debug.LogMessage(LogEventLevel.Debug, "Cannot find audio route to {0}", destination, source.Key); - if (!videoSuccess) - Debug.LogMessage(LogEventLevel.Debug, "Cannot find video route to {0}", destination, source.Key); + var videoSuccess = destination.GetRouteToSource(source, sourcePort, null, eRoutingSignalType.Video, 0, routeDescriptor, destinationPort); - if (!audioSuccess && !videoSuccess) - routeDescriptor = null; - - - return routeDescriptor; - } + if (!videoSuccess) + Debug.LogMessage(LogEventLevel.Debug, "Cannot find video route to {0}", destination, source.Key); - /// - /// The recursive part of this. Will stop on each device, search its inputs for the - /// desired source and if not found, invoke this function for the each input port - /// hoping to find the source. - /// - /// - /// - /// The RoutingOutputPort whose link is being checked for a route - /// Prevents Devices from being twice-checked - /// This recursive function should not be called with AudioVideo - /// Just an informational counter - /// The RouteDescriptor being populated as the route is discovered - /// true if source is hit - static bool GetRouteToSource(this IRoutingInputs destination, IRoutingOutputs source, - RoutingOutputPort outputPortToUse, List alreadyCheckedDevices, - eRoutingSignalType signalType, int cycle, RouteDescriptor routeTable) - { - cycle++; + if (!audioSuccess && !videoSuccess) + routeDescriptor = null; - Debug.LogMessage(LogEventLevel.Verbose, "GetRouteToSource: {0} {1}--> {2}", null, cycle, source.Key, destination.Key); - RoutingInputPort goodInputPort = null; + return routeDescriptor; + } - var destinationTieLines = TieLineCollection.Default.Where(t => - t.DestinationPort.ParentDevice == destination && (t.Type == signalType || t.Type.HasFlag(eRoutingSignalType.AudioVideo))); + /// + /// The recursive part of this. Will stop on each device, search its inputs for the + /// desired source and if not found, invoke this function for the each input port + /// hoping to find the source. + /// + /// + /// + /// The RoutingOutputPort whose link is being checked for a route + /// Prevents Devices from being twice-checked + /// This recursive function should not be called with AudioVideo + /// Just an informational counter + /// The RouteDescriptor being populated as the route is discovered + /// true if source is hit + private static bool GetRouteToSource(this IRoutingInputs destination, IRoutingOutputs source, + RoutingOutputPort sourcePort, List alreadyCheckedDevices, + eRoutingSignalType signalType, int cycle, RouteDescriptor routeTable, RoutingInputPort destinationPort) + { + cycle++; - // 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 - { - goodInputPort = directTie.DestinationPort; - } - else // no direct-connect. Walk back devices. - { - Debug.LogMessage(LogEventLevel.Verbose, "is not directly connected to {0}. Walking down tie lines", destination, source.Key); + Debug.LogMessage(LogEventLevel.Verbose, "GetRouteToSource: {0} {1}--> {2}", null, cycle, source.Key, destination.Key); - // No direct tie? Run back out on the inputs' attached devices... - // Only the ones that are routing devices - var attachedMidpoints = destinationTieLines.Where(t => t.SourcePort.ParentDevice is IRoutingInputsOutputs); + RoutingInputPort goodInputPort = null; + + IEnumerable destinationTieLines; + 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); + } + + if (directTie != null) // Found a tie directly to the source + { + goodInputPort = directTie.DestinationPort; + } + else // no direct-connect. Walk back devices. + { + Debug.LogMessage(LogEventLevel.Verbose, "is not directly connected to {0}. Walking down tie lines", destination, source.Key); + + // No direct tie? Run back out on the inputs' attached devices... + // Only the ones that are routing devices + 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 if (alreadyCheckedDevices == null) alreadyCheckedDevices = new List(); alreadyCheckedDevices.Add(destination as IRoutingInputsOutputs); - foreach (var inputTieToTry in attachedMidpoints) - { - var upstreamDeviceOutputPort = inputTieToTry.SourcePort; - var upstreamRoutingDevice = upstreamDeviceOutputPort.ParentDevice as IRoutingInputsOutputs; - Debug.LogMessage(LogEventLevel.Verbose, "Trying to find route on {0}", destination, upstreamRoutingDevice.Key); + foreach (var tieLine in midpointTieLines) + { + var midpointDevice = tieLine.SourcePort.ParentDevice as IRoutingInputsOutputs; - // Check if this previous device has already been walked - if (alreadyCheckedDevices.Contains(upstreamRoutingDevice)) + // Check if this previous device has already been walked + 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; } + + 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 // level to enable switching on success - var upstreamRoutingSuccess = upstreamRoutingDevice.GetRouteToSource(source, upstreamDeviceOutputPort, - alreadyCheckedDevices, signalType, cycle, routeTable); + var upstreamRoutingSuccess = midpointDevice.GetRouteToSource(source, midpointOutputPort, + alreadyCheckedDevices, signalType, cycle, routeTable, null); + if (upstreamRoutingSuccess) { Debug.LogMessage(LogEventLevel.Verbose, "Upstream device route found", destination); - goodInputPort = inputTieToTry.DestinationPort; + goodInputPort = tieLine.DestinationPort; break; // Stop looping the inputs in this cycle } - } - } + } + } - - if (goodInputPort == null) - { + + if (goodInputPort == null) + { Debug.LogMessage(LogEventLevel.Verbose, "No route found to {0}", destination, source.Key); return false; - } + } // we have a route on corresponding inputPort. *** Do the route *** - if (outputPortToUse == null) + if (sourcePort == null) { // it's a sink device routeTable.Routes.Add(new RouteSwitchDescriptor(goodInputPort)); } else if (destination is IRouting) { - routeTable.Routes.Add(new RouteSwitchDescriptor(outputPortToUse, goodInputPort)); + routeTable.Routes.Add(new RouteSwitchDescriptor(sourcePort, goodInputPort)); } else // device is merely IRoutingInputOutputs Debug.LogMessage(LogEventLevel.Verbose, "No routing. Passthrough device", destination); - + return true; } - } + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingSource.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingSource.cs index f4705b8d..d50bcbb0 100644 --- a/src/PepperDash.Essentials.Core/Routing/IRoutingSource.cs +++ b/src/PepperDash.Essentials.Core/Routing/IRoutingSource.cs @@ -3,7 +3,7 @@ /// /// Defines an IRoutingOutputs devices as being a source - the start of the chain /// - public interface IRoutingSource - { - } + public interface IRoutingSource : IRoutingOutputs + { + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/RouteRequest.cs b/src/PepperDash.Essentials.Core/Routing/RouteRequest.cs index f4836c6d..35169474 100644 --- a/src/PepperDash.Essentials.Core/Routing/RouteRequest.cs +++ b/src/PepperDash.Essentials.Core/Routing/RouteRequest.cs @@ -2,19 +2,22 @@ { public class RouteRequest { - public IRoutingSink Destination {get; set;} - public IRoutingOutputs Source {get; set;} - public eRoutingSignalType SignalType {get; set;} + public RoutingInputPort DestinationPort { 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) { var coolingDevice = sender as IWarmingCooling; - - if(args.BoolValue == false) + + if (args.BoolValue == false) { Destination.ReleaseAndMakeRoute(Source, SignalType); - - if(sender == null) return; + + if (sender == null) return; coolingDevice.IsCoolingDownFeedback.OutputChange -= HandleCooldown; }