feat: update RoutingFeedbackManager to map downstream sink input ports and optimize performance in route updates

This commit is contained in:
Neil Dorin 2026-07-02 10:53:51 -06:00
parent fb4ba8ccfa
commit 9f18275f81

View file

@ -14,9 +14,12 @@ namespace PepperDash.Essentials.Core.Routing
public class RoutingFeedbackManager : EssentialsDevice public class RoutingFeedbackManager : EssentialsDevice
{ {
/// <summary> /// <summary>
/// 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. <see cref="IRoutingSinkWithFeedback.CurrentInputPort"/>
/// stays null, such as codecs fed from an external matrix) are still mapped and updated.
/// </summary> /// </summary>
private Dictionary<string, HashSet<string>> midpointToSinksMap; private Dictionary<string, HashSet<RoutingInputPort>> midpointToSinkInputsMap;
/// <summary> /// <summary>
/// Debounce timers for each sink device to prevent rapid successive updates /// Debounce timers for each sink device to prevent rapid successive updates
@ -42,30 +45,39 @@ namespace PepperDash.Essentials.Core.Routing
} }
/// <summary> /// <summary>
/// Builds a map of which sink devices are downstream of each midpoint device /// Builds a map of which sink input ports are downstream of each midpoint device
/// for performance optimization in HandleMidpointUpdate /// 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.
/// </summary> /// </summary>
private void BuildMidpointSinkMap() private void BuildMidpointSinkMap()
{ {
midpointToSinksMap = new Dictionary<string, HashSet<string>>(); midpointToSinkInputsMap = new Dictionary<string, HashSet<RoutingInputPort>>();
var sinks = DeviceManager.AllDevices.OfType<IRoutingSinkWithFeedback>(); var sinks = DeviceManager.AllDevices.OfType<IRoutingSinkWithFeedback>();
var midpoints = DeviceManager.AllDevices.OfType<IRoutingMidpointWithFeedback>();
foreach (var sink in sinks) 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; continue;
// Find all upstream midpoints for this sink foreach (var inputPort in inputPorts)
var upstreamMidpoints = GetUpstreamMidpoints(sink); {
var upstreamMidpoints = GetUpstreamMidpointsForInput(inputPort);
foreach (var midpointKey in upstreamMidpoints) foreach (var midpointKey in upstreamMidpoints)
{ {
if (!midpointToSinksMap.ContainsKey(midpointKey)) if (!midpointToSinkInputsMap.TryGetValue(midpointKey, out var inputs))
midpointToSinksMap[midpointKey] = new HashSet<string>(); {
inputs = new HashSet<RoutingInputPort>();
midpointToSinkInputsMap[midpointKey] = inputs;
}
midpointToSinksMap[midpointKey].Add(sink.Key); inputs.Add(inputPort);
}
} }
} }
@ -73,24 +85,25 @@ namespace PepperDash.Essentials.Core.Routing
Serilog.Events.LogEventLevel.Information, Serilog.Events.LogEventLevel.Information,
"Built midpoint-to-sink map with {count} midpoints", "Built midpoint-to-sink map with {count} midpoints",
this, this,
midpointToSinksMap.Count midpointToSinkInputsMap.Count
); );
} }
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
private HashSet<string> GetUpstreamMidpoints(IRoutingSinkWithFeedback sink) private HashSet<string> GetUpstreamMidpointsForInput(RoutingInputPort inputPort)
{ {
var result = new HashSet<string>(); var result = new HashSet<string>();
var visited = new HashSet<string>(); var visited = new HashSet<string>();
if (sink.CurrentInputPort == null) if (inputPort == null)
return result; return result;
var tieLine = TieLineCollection.Default.FirstOrDefault(tl => var tieLine = TieLineCollection.Default.FirstOrDefault(tl =>
tl.DestinationPort.Key == sink.CurrentInputPort.Key && tl.DestinationPort.Key == inputPort.Key &&
tl.DestinationPort.ParentDevice.Key == sink.CurrentInputPort.ParentDevice.Key); tl.DestinationPort.ParentDevice.Key == inputPort.ParentDevice.Key);
if (tieLine == null) if (tieLine == null)
return result; return result;
@ -171,22 +184,34 @@ namespace PepperDash.Essentials.Core.Routing
try try
{ {
// Only update affected sinks (performance optimization) // 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( Debug.LogMessage(
Serilog.Events.LogEventLevel.Debug, Serilog.Events.LogEventLevel.Debug,
"Midpoint {midpoint} changed, updating {count} downstream sinks", "Midpoint {midpoint} changed, updating {count} downstream sink inputs",
this, this,
midpoint.Key, 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<string>();
foreach (var inputPort in affectedInputPorts)
{ {
if (DeviceManager.GetDeviceForKey(sinkKey) is IRoutingSinkWithFeedback sink) if (!(inputPort.ParentDevice is IRoutingSinkWithFeedback sink))
{ continue;
UpdateDestination(sink, sink.CurrentInputPort);
} // 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 else