diff --git a/src/PepperDash.Essentials.Core/Routing/RoutingFeedbackManager.cs b/src/PepperDash.Essentials.Core/Routing/RoutingFeedbackManager.cs
index 0eb741bb..eac9b83a 100644
--- a/src/PepperDash.Essentials.Core/Routing/RoutingFeedbackManager.cs
+++ b/src/PepperDash.Essentials.Core/Routing/RoutingFeedbackManager.cs
@@ -14,9 +14,12 @@ namespace PepperDash.Essentials.Core.Routing
public class RoutingFeedbackManager : EssentialsDevice
{
///
- /// Maps midpoint device keys to the set of sink device keys that are downstream
+ /// Maps midpoint device keys to the set of downstream sink input ports, derived from the
+ /// static tie-line topology. Because it is built from topology rather than a sink's currently
+ /// reported input, sinks that never report an input (i.e.
+ /// stays null, such as codecs fed from an external matrix) are still mapped and updated.
///
- private Dictionary> midpointToSinksMap;
+ private Dictionary> midpointToSinkInputsMap;
///
/// Debounce timers for each sink device to prevent rapid successive updates
@@ -42,30 +45,39 @@ namespace PepperDash.Essentials.Core.Routing
}
///
- /// Builds a map of which sink devices are downstream of each midpoint device
- /// for performance optimization in HandleMidpointUpdate
+ /// Builds a map of which sink input ports are downstream of each midpoint device
+ /// for performance optimization in HandleMidpointUpdate.
+ /// The map is derived from the static tie-line topology (every sink input port is traced
+ /// upstream), so it does not depend on a sink having already reported its current input.
///
private void BuildMidpointSinkMap()
{
- midpointToSinksMap = new Dictionary>();
+ midpointToSinkInputsMap = new Dictionary>();
var sinks = DeviceManager.AllDevices.OfType();
- var midpoints = DeviceManager.AllDevices.OfType();
foreach (var sink in sinks)
{
- if (sink.CurrentInputPort == null)
+ // Trace from every input port on the sink (static topology) rather than only the
+ // currently-selected input. Sinks that never report an input still get mapped.
+ var inputPorts = (sink as IRoutingInputs)?.InputPorts;
+ if (inputPorts == null)
continue;
- // Find all upstream midpoints for this sink
- var upstreamMidpoints = GetUpstreamMidpoints(sink);
-
- foreach (var midpointKey in upstreamMidpoints)
+ foreach (var inputPort in inputPorts)
{
- if (!midpointToSinksMap.ContainsKey(midpointKey))
- midpointToSinksMap[midpointKey] = new HashSet();
+ var upstreamMidpoints = GetUpstreamMidpointsForInput(inputPort);
- midpointToSinksMap[midpointKey].Add(sink.Key);
+ foreach (var midpointKey in upstreamMidpoints)
+ {
+ if (!midpointToSinkInputsMap.TryGetValue(midpointKey, out var inputs))
+ {
+ inputs = new HashSet();
+ midpointToSinkInputsMap[midpointKey] = inputs;
+ }
+
+ inputs.Add(inputPort);
+ }
}
}
@@ -73,24 +85,25 @@ namespace PepperDash.Essentials.Core.Routing
Serilog.Events.LogEventLevel.Information,
"Built midpoint-to-sink map with {count} midpoints",
this,
- midpointToSinksMap.Count
+ midpointToSinkInputsMap.Count
);
}
///
- /// Gets all upstream midpoint device keys for a given sink
+ /// Gets all upstream midpoint device keys reachable from a specific sink input port
+ /// by walking the static tie-line topology.
///
- private HashSet GetUpstreamMidpoints(IRoutingSinkWithFeedback sink)
+ private HashSet GetUpstreamMidpointsForInput(RoutingInputPort inputPort)
{
var result = new HashSet();
var visited = new HashSet();
- if (sink.CurrentInputPort == null)
+ if (inputPort == null)
return result;
var tieLine = TieLineCollection.Default.FirstOrDefault(tl =>
- tl.DestinationPort.Key == sink.CurrentInputPort.Key &&
- tl.DestinationPort.ParentDevice.Key == sink.CurrentInputPort.ParentDevice.Key);
+ tl.DestinationPort.Key == inputPort.Key &&
+ tl.DestinationPort.ParentDevice.Key == inputPort.ParentDevice.Key);
if (tieLine == null)
return result;
@@ -171,22 +184,34 @@ namespace PepperDash.Essentials.Core.Routing
try
{
// Only update affected sinks (performance optimization)
- if (midpointToSinksMap != null && midpointToSinksMap.TryGetValue(midpoint.Key, out var affectedSinkKeys))
+ if (midpointToSinkInputsMap != null && midpointToSinkInputsMap.TryGetValue(midpoint.Key, out var affectedInputPorts))
{
Debug.LogMessage(
Serilog.Events.LogEventLevel.Debug,
- "Midpoint {midpoint} changed, updating {count} downstream sinks",
+ "Midpoint {midpoint} changed, updating {count} downstream sink inputs",
this,
midpoint.Key,
- affectedSinkKeys.Count
+ affectedInputPorts.Count
);
- foreach (var sinkKey in affectedSinkKeys)
+ // Avoid redundant updates when a feedback-reporting sink has several mapped inputs.
+ var updatedSinkKeys = new HashSet();
+
+ foreach (var inputPort in affectedInputPorts)
{
- if (DeviceManager.GetDeviceForKey(sinkKey) is IRoutingSinkWithFeedback sink)
- {
- UpdateDestination(sink, sink.CurrentInputPort);
- }
+ if (!(inputPort.ParentDevice is IRoutingSinkWithFeedback sink))
+ continue;
+
+ // Sinks that report their input drive updates off the currently-selected input.
+ // Sinks that never report an input (CurrentInputPort == null, e.g. a codec fed
+ // from an external matrix) fall back to the static topology input port so that
+ // matrix route changes still propagate.
+ var portToUse = sink.CurrentInputPort ?? inputPort;
+
+ if (sink.CurrentInputPort != null && !updatedSinkKeys.Add(sink.Key))
+ continue;
+
+ UpdateDestination(sink, portToUse);
}
}
else