feat: enhance routing functionality with additional parameters in RouteDescriptor and update RouteDescriptorCollection for change notifications

This commit is contained in:
Neil Dorin 2026-07-01 17:14:32 -06:00
parent 4bb2917b69
commit fb4ba8ccfa
5 changed files with 171 additions and 200 deletions

View file

@ -222,7 +222,7 @@ public static class Extensions
// if it's a single signal type, find the route
if (!signalType.HasFlag(eRoutingSignalType.AudioVideo))
{
var singleTypeRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, signalType);
var singleTypeRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, sourcePort, signalType);
Debug.LogMessage(LogEventLevel.Debug, "Attempting to build source route from {sourceKey} of type {type}", destination, source.Key, signalType);
if (!destination.GetRouteToSource(source, null, null, signalType, 0, singleTypeRouteDescriptor, destinationPort, sourcePort))
@ -240,14 +240,14 @@ public static class Extensions
Debug.LogMessage(LogEventLevel.Debug, "Attempting to build source route from {sourceKey} of type {type}", destination, source.Key);
var audioRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, eRoutingSignalType.Audio);
var audioRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, sourcePort, eRoutingSignalType.Audio);
var audioSuccess = destination.GetRouteToSource(source, null, null, eRoutingSignalType.Audio, 0, audioRouteDescriptor, destinationPort, sourcePort);
if (!audioSuccess)
Debug.LogMessage(LogEventLevel.Debug, "Cannot find audio route to {0}", destination, source.Key);
var videoRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, eRoutingSignalType.Video);
var videoRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, sourcePort, eRoutingSignalType.Video);
var videoSuccess = destination.GetRouteToSource(source, null, null, eRoutingSignalType.Video, 0, videoRouteDescriptor, destinationPort, sourcePort);
@ -366,7 +366,8 @@ public static class Extensions
audioOrSingleRoute = audioCollection.Descriptors.FirstOrDefault(d =>
d.Source.Key == request.Source.Key &&
d.Destination.Key == request.Destination.Key &&
(request.DestinationPort == null || d.InputPort?.Key == request.DestinationPort.Key));
(request.DestinationPort == null || d.InputPort?.Key == request.DestinationPort.Key) &&
(request.SourcePort == null || d.OutputPort?.Key == request.SourcePort.Key));
}
if (RouteDescriptors.TryGetValue(eRoutingSignalType.Video, out RouteDescriptorCollection videoCollection))
@ -374,7 +375,8 @@ public static class Extensions
videoRoute = videoCollection.Descriptors.FirstOrDefault(d =>
d.Source.Key == request.Source.Key &&
d.Destination.Key == request.Destination.Key &&
(request.DestinationPort == null || d.InputPort?.Key == request.DestinationPort.Key));
(request.DestinationPort == null || d.InputPort?.Key == request.DestinationPort.Key) &&
(request.SourcePort == null || d.OutputPort?.Key == request.SourcePort.Key));
}
}
else
@ -385,7 +387,8 @@ public static class Extensions
audioOrSingleRoute = collection.Descriptors.FirstOrDefault(d =>
d.Source.Key == request.Source.Key &&
d.Destination.Key == request.Destination.Key &&
(request.DestinationPort == null || d.InputPort?.Key == request.DestinationPort.Key));
(request.DestinationPort == null || d.InputPort?.Key == request.DestinationPort.Key) &&
(request.SourcePort == null || d.OutputPort?.Key == request.SourcePort.Key));
}
}

View file

@ -7,8 +7,8 @@ using PepperDash.Core;
using Serilog.Events;
namespace PepperDash.Essentials.Core;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Represents a collection of individual route steps between a Source and a Destination device for a specific signal type.
/// </summary>
@ -20,22 +20,27 @@ public class RouteDescriptor
public IRoutingInputs Destination { get; private set; }
/// <summary>
/// The specific input port on the destination device used for this route. Can be null if not specified or applicable.
/// The InputPort on the destination device for this route, if applicable. May be null if the route is not for a specific input port.
/// </summary>
public RoutingInputPort InputPort { get; private set; }
/// <summary>
/// The source device for the route.
/// Gets the source device (sink or midpoint) for the route.
/// </summary>
public IRoutingOutputs Source { get; private set; }
/// <summary>
/// The type of signal being routed (e.g., Audio, Video). This descriptor represents a single signal type.
/// Gets the OutputPort on the source device for this route, if applicable. May be null if the route is not for a specific output port.
/// </summary>
public RoutingOutputPort OutputPort { get; private set; }
/// <summary>
/// Gets the signal type for this route.
/// </summary>
public eRoutingSignalType SignalType { get; private set; }
/// <summary>
/// A list of individual switching steps required to establish the route.
/// Gets the collection of route switch descriptors for this route.
/// </summary>
public List<RouteSwitchDescriptor> Routes { get; private set; }
@ -56,23 +61,36 @@ public class RouteDescriptor
/// <param name="destination">The destination device.</param>
/// <param name="inputPort">The destination input port (optional).</param>
/// <param name="signalType">The signal type for this route.</param>
public RouteDescriptor(IRoutingOutputs source, IRoutingInputs destination, RoutingInputPort inputPort, eRoutingSignalType signalType)
public RouteDescriptor(IRoutingOutputs source, IRoutingInputs destination, RoutingInputPort inputPort, eRoutingSignalType signalType) : this(source, destination, inputPort, null, signalType)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RouteDescriptor"/> class for a route with specific destination input and source output ports.
/// </summary>
/// <param name="source"></param>
/// <param name="destination"></param>
/// <param name="inputPort"></param>
/// <param name="outputPort"></param>
/// <param name="signalType"></param>
public RouteDescriptor(IRoutingOutputs source, IRoutingInputs destination, RoutingInputPort inputPort, RoutingOutputPort outputPort, eRoutingSignalType signalType)
{
Destination = destination;
InputPort = inputPort;
Source = source;
OutputPort = outputPort;
SignalType = signalType;
Routes = new List<RouteSwitchDescriptor>();
}
/// <summary>
/// Executes all the switching steps defined in the <see cref="Routes"/> list.
/// ExecuteRoutes method
/// </summary>
public void ExecuteRoutes()
{
foreach (var route in Routes)
{
Debug.LogMessage(LogEventLevel.Verbose, "ExecuteRoutes: {0}", null, route.ToString());
Debug.LogVerbose("ExecuteRoutes: {0}", route.ToString());
if (route.SwitchingDevice is IRoutingSinkWithFeedback sink)
{
@ -86,7 +104,7 @@ public class RouteDescriptor
route.OutputPort.InUseTracker.AddUser(Destination, "destination-" + SignalType);
Debug.LogMessage(LogEventLevel.Verbose, "Output port {0} routing. Count={1}", null, route.OutputPort.Key, route.OutputPort.InUseTracker.InUseCountFeedback.UShortValue);
Debug.LogVerbose("Output port {0} routing. Count={1}", route.OutputPort.Key, route.OutputPort.InUseTracker.InUseCountFeedback.UShortValue);
}
}
}
@ -95,6 +113,8 @@ public class RouteDescriptor
/// Releases the usage tracking for the route and optionally clears the route on the switching devices.
/// </summary>
/// <param name="clearRoute">If true, attempts to clear the route on the switching devices (e.g., set input to null/0).</param>
public void ReleaseRoutes(bool clearRoute = false)
{
foreach (var route in Routes.Where(r => r.SwitchingDevice is IRoutingMidpointWithFeedback))
@ -110,6 +130,7 @@ public class RouteDescriptor
catch (Exception e)
{
Debug.LogError("Error executing switch: {exception}", e.Message);
Debug.LogDebug(e, "Stack Trace: ");
}
}
@ -121,11 +142,11 @@ public class RouteDescriptor
if (route.OutputPort.InUseTracker != null)
{
route.OutputPort.InUseTracker.RemoveUser(Destination, "destination-" + SignalType);
Debug.LogMessage(LogEventLevel.Verbose, "Port {0} releasing. Count={1}", null, route.OutputPort.Key, route.OutputPort.InUseTracker.InUseCountFeedback.UShortValue);
Debug.LogVerbose("Port {0} releasing. Count={1}", route.OutputPort.Key, route.OutputPort.InUseTracker.InUseCountFeedback.UShortValue);
}
else
{
Debug.LogMessage(LogEventLevel.Error, "InUseTracker is null for OutputPort {0}", null, route.OutputPort.Key);
Debug.LogVerbose("InUseTracker is null for OutputPort {0}", route.OutputPort.Key);
}
}
}
@ -141,3 +162,5 @@ public class RouteDescriptor
return $"Route table from {Source.Key} to {Destination.Key} for {SignalType}:\r\n {string.Join("\r\n ", routesText)}";
}
}
}

View file

@ -28,6 +28,9 @@ public class RouteDescriptorCollection
private readonly List<RouteDescriptor> RouteDescriptors = new List<RouteDescriptor>();
/// <summary>
/// Event raised when the collection of RouteDescriptors changes (add/remove). This is useful for updating routing status in the UI, for example.
/// </summary>
public event EventHandler RouteDescriptorCollectionChanged;
/// <summary>

View file

@ -2,6 +2,7 @@
{
/// <summary>
/// Represents a RouteSwitchDescriptor
/// This represents a switch to be made on an IRoutingInputs device, which could be a matrix switcher or a sink device.
/// </summary>
public class RouteSwitchDescriptor
{

View file

@ -225,6 +225,7 @@ namespace PepperDash.Essentials.Core.Routing
try
{
UpdateDestination(sender, currentInputPort);
}
catch (Exception ex)
{
@ -340,11 +341,6 @@ namespace PepperDash.Essentials.Core.Routing
inputPort
);
var tempSourceListItem = new SourceListItem
{
SourceKey = "$transient",
Name = inputPort.Key,
};
return;
}
@ -371,11 +367,15 @@ namespace PepperDash.Essentials.Core.Routing
inputPort
);
var tempSourceListItem = new SourceListItem
{
SourceKey = "$transient",
Name = "None",
};
// determine all the tie lines between the source and destination to determine the signal type
// the type is the union of all the tie lines between the source and destination
// For now we assume the type matches the tie line connected to the destination
destination.SetCurrentSource(firstTieLine.Type, null);
// remove existing descriptor if any
RouteDescriptorCollection.DefaultCollection.RemoveRouteDescriptor(destination, inputPort.Key);
return;
}
@ -386,83 +386,24 @@ namespace PepperDash.Essentials.Core.Routing
return;
}
// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Found root TieLine {tieLine}", this, sourceTieLine);
// Does not handle combinable scenarios or other scenarios where a display might be part of multiple rooms yet.
var room = DeviceManager
.AllDevices.OfType<IEssentialsRoom>()
.FirstOrDefault(
(r) =>
{
if (r is IHasDefaultDisplay roomDefaultDisplay)
{
return roomDefaultDisplay.DefaultDisplay.Key == destination.Key;
}
return false;
}
// Get the routes from the destination to the source using the existing GetRouteToSource method
var routes = destination.GetRouteToSource(
sourceTieLine.SourcePort.ParentDevice as IRoutingOutputs,
sourceTieLine.Type,
inputPort,
sourceTieLine.SourcePort
);
if (room == null)
// remove existing descriptor if any
RouteDescriptorCollection.DefaultCollection.RemoveRouteDescriptor(destination, inputPort.Key);
// Add the new route descriptors to the collection
RouteDescriptorCollection.DefaultCollection.AddRouteDescriptor(routes.Item1);
if(routes.Item2 != null)
{
Debug.LogMessage(
Serilog.Events.LogEventLevel.Debug,
"No room found for display {destination}",
this,
destination.Key
);
return;
}
// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Found room {room} for destination {destination}", this, room.Key, destination.Key);
var sourceList = ConfigReader.ConfigObject.GetSourceListForKey(room.SourceListKey);
if (sourceList == null)
{
Debug.LogMessage(
Serilog.Events.LogEventLevel.Debug,
"No source list found for source list key {key}. Unable to find source for tieLine {sourceTieLine}",
this,
room.SourceListKey,
sourceTieLine
);
return;
}
// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Found sourceList for room {room}", this, room.Key);
var sourceListItem = sourceList.FirstOrDefault(sli =>
{
//// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose,
// "SourceListItem {sourceListItem}:{sourceKey} tieLine sourceport device key {sourcePortDeviceKey}",
// this,
// sli.Key,
// sli.Value.SourceKey,
// sourceTieLine.SourcePort.ParentDevice.Key);
return sli.Value.SourceKey.Equals(
sourceTieLine.SourcePort.ParentDevice.Key,
StringComparison.InvariantCultureIgnoreCase
);
});
var source = sourceListItem.Value;
var sourceKey = sourceListItem.Key;
if (source == null)
{
Debug.LogMessage(
Serilog.Events.LogEventLevel.Debug,
"No source found for device {key}. Creating transient source for {destination}",
this,
sourceTieLine.SourcePort.ParentDevice.Key,
destination
);
return;
RouteDescriptorCollection.DefaultCollection.AddRouteDescriptor(routes.Item2);
}
}