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

@@ -12,20 +12,32 @@ namespace PepperDash.Essentials.Core
/// on those destinations. /// on those destinations.
/// </summary> /// </summary>
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>
/// Gets any existing RouteDescriptor for a destination, clears it using ReleaseRoute /// <summary>
/// and then attempts a new Route and if sucessful, stores that RouteDescriptor /// Gets any existing RouteDescriptor for a destination, clears it using ReleaseRoute
/// in RouteDescriptorCollection.DefaultCollection /// and then attempts a new Route and if sucessful, stores that RouteDescriptor
/// </summary> /// in RouteDescriptorCollection.DefaultCollection
public static void ReleaseAndMakeRoute(this IRoutingSink destination, IRoutingOutputs source, eRoutingSignalType signalType) /// </summary>
{ public static void ReleaseAndMakeRoute(this IRoutingInputs destination, IRoutingOutputs source, eRoutingSignalType signalType, string destinationPortKey = "", string sourcePortKey = "")
var routeRequest = new RouteRequest { {
Destination = destination, var inputPort = string.IsNullOrEmpty(destinationPortKey) ? null : destination.InputPorts.FirstOrDefault(p => p.Key == destinationPortKey);
Source = source, var outputPort = string.IsNullOrEmpty(sourcePortKey) ? null : source.OutputPorts.FirstOrDefault(p => p.Key == sourcePortKey);
SignalType = signalType
}; 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; var coolingDevice = destination as IWarmingCooling;
@@ -65,15 +77,15 @@ namespace PepperDash.Essentials.Core
destination.ReleaseRoute(); destination.ReleaseRoute();
RunRouteRequest(routeRequest); RunRouteRequest(routeRequest);
} }
private static void RunRouteRequest(RouteRequest request) private static void RunRouteRequest(RouteRequest request)
{ {
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;
@@ -85,13 +97,13 @@ namespace PepperDash.Essentials.Core
newRoute.ExecuteRoutes(); newRoute.ExecuteRoutes();
} }
/// <summary> /// <summary>
/// Will release the existing route on the destination, if it is found in /// Will release the existing route on the destination, if it is found in
/// 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)
{ {
@@ -102,150 +114,182 @@ namespace PepperDash.Essentials.Core
RouteRequests.Remove(destination.Key); RouteRequests.Remove(destination.Key);
var current = RouteDescriptorCollection.DefaultCollection.RemoveRouteDescriptor(destination); var current = RouteDescriptorCollection.DefaultCollection.RemoveRouteDescriptor(destination);
if (current != null) if (current != null)
{ {
Debug.LogMessage(LogEventLevel.Debug, "Releasing current route: {0}", destination, current.Source.Key); Debug.LogMessage(LogEventLevel.Debug, "Releasing current route: {0}", destination, current.Source.Key);
current.ReleaseRoutes(); current.ReleaseRoutes();
} }
} }
/// <summary> /// <summary>
/// Builds a RouteDescriptor that contains the steps necessary to make a route between devices. /// 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 /// 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 /// 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 /// 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);
// if it's a single signal type, find the route // if it's a single signal type, find the route
if (!signalType.HasFlag(eRoutingSignalType.AudioVideo)) if (!signalType.HasFlag(eRoutingSignalType.AudioVideo))
{ {
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;
} }
// otherwise, audioVideo needs to be handled as two steps. // 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);
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) var audioSuccess = destination.GetRouteToSource(source, sourcePort, null, eRoutingSignalType.Audio, 0, routeDescriptor, destinationPort);
Debug.LogMessage(LogEventLevel.Debug, "Cannot find audio route to {0}", destination, source.Key);
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) var videoSuccess = destination.GetRouteToSource(source, sourcePort, null, eRoutingSignalType.Video, 0, routeDescriptor, destinationPort);
Debug.LogMessage(LogEventLevel.Debug, "Cannot find video route to {0}", destination, source.Key);
if (!audioSuccess && !videoSuccess) if (!videoSuccess)
routeDescriptor = null; Debug.LogMessage(LogEventLevel.Debug, "Cannot find video route to {0}", destination, source.Key);
return routeDescriptor;
}
/// <summary> if (!audioSuccess && !videoSuccess)
/// The recursive part of this. Will stop on each device, search its inputs for the routeDescriptor = null;
/// desired source and if not found, invoke this function for the each input port
/// hoping to find the source.
/// </summary>
/// <param name="destination"></param>
/// <param name="source"></param>
/// <param name="outputPortToUse">The RoutingOutputPort whose link is being checked for a route</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="cycle">Just an informational counter</param>
/// <param name="routeTable">The RouteDescriptor being populated as the route is discovered</param>
/// <returns>true if source is hit</returns>
static bool GetRouteToSource(this IRoutingInputs destination, IRoutingOutputs source,
RoutingOutputPort outputPortToUse, List<IRoutingInputsOutputs> alreadyCheckedDevices,
eRoutingSignalType signalType, int cycle, RouteDescriptor routeTable)
{
cycle++;
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 => /// <summary>
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.
/// </summary>
/// <param name="destination"></param>
/// <param name="source"></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="signalType">This recursive function should not be called with AudioVideo</param>
/// <param name="cycle">Just an informational counter</param>
/// <param name="routeTable">The RouteDescriptor being populated as the route is discovered</param>
/// <returns>true if source is hit</returns>
private static bool GetRouteToSource(this IRoutingInputs destination, IRoutingOutputs source,
RoutingOutputPort sourcePort, List<IRoutingInputsOutputs> alreadyCheckedDevices,
eRoutingSignalType signalType, int cycle, RouteDescriptor routeTable, RoutingInputPort destinationPort)
{
cycle++;
// find a direct tie Debug.LogMessage(LogEventLevel.Verbose, "GetRouteToSource: {0} {1}--> {2}", null, cycle, source.Key, destination.Key);
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);
// No direct tie? Run back out on the inputs' attached devices... RoutingInputPort goodInputPort = null;
// Only the ones that are routing devices
var attachedMidpoints = destinationTieLines.Where(t => t.SourcePort.ParentDevice is IRoutingInputsOutputs); IEnumerable<TieLine> 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 //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
} }
} }
} }
if (goodInputPort == null) if (goodInputPort == null)
{ {
Debug.LogMessage(LogEventLevel.Verbose, "No route found to {0}", destination, source.Key); Debug.LogMessage(LogEventLevel.Verbose, "No route found to {0}", destination, source.Key);
return false; return false;
} }
// 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);
return true; return true;
} }
} }
} }

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