Merge pull request #1214 from PepperDash/feature-2.0.0/concurrent-routing-issues

Fix issues with concurrent routing actions
This commit is contained in:
Jason T Alborough
2025-02-20 13:19:56 -05:00
committed by GitHub
7 changed files with 190 additions and 134 deletions

View File

@@ -26,7 +26,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Crestron.SimplSharp.SDK.ProgramLibrary" Version="2.20.66" />
<PackageReference Include="PepperDashCore" Version="2.0.0-alpha-424" />
<PackageReference Include="PepperDashCore" Version="2.0.0-alpha-451" />
</ItemGroup>
<ItemGroup>
<None Include="Crestron\CrestronGenericBaseDevice.cs.orig" />

View File

@@ -1,4 +1,6 @@
using Serilog.Events;
using PepperDash.Essentials.Core.Queues;
using PepperDash.Essentials.Core.Routing;
using Serilog.Events;
using System;
using System.Collections.Generic;
using System.Diagnostics;
@@ -17,6 +19,8 @@ namespace PepperDash.Essentials.Core
{
private static readonly Dictionary<string, RouteRequest> RouteRequests = new Dictionary<string, RouteRequest>();
private static readonly GenericQueue routeRequestQueue = new GenericQueue("routingQueue");
/// <summary>
/// Gets any existing RouteDescriptor for a destination, clears it using ReleaseRoute
/// and then attempts a new Route and if sucessful, stores that RouteDescriptor
@@ -33,6 +37,15 @@ namespace PepperDash.Essentials.Core
ReleaseAndMakeRoute(destination, source, signalType, inputPort, outputPort);
}
public static void ReleaseRoute(this IRoutingInputs destination)
{
routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination, string.Empty));
}
public static void ReleaseRoute(this IRoutingInputs destination, string inputPortKey)
{
routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination, inputPortKey));
}
public static void RemoveRouteRequestForDestination(string destinationKey)
{
@@ -45,133 +58,6 @@ namespace PepperDash.Essentials.Core
Debug.LogMessage(LogEventLevel.Information, messageTemplate, null, destinationKey);
}
private static void ReleaseAndMakeRoute(IRoutingInputs destination, IRoutingOutputs source, eRoutingSignalType signalType, RoutingInputPort destinationPort = null, RoutingOutputPort sourcePort = null)
{
if (destination == null) throw new ArgumentNullException(nameof(destination));
if (source == null) throw new ArgumentNullException(nameof(source));
if (destinationPort == null) Debug.LogMessage(LogEventLevel.Information, "Destination port is null");
if (sourcePort == null) Debug.LogMessage(LogEventLevel.Information, "Source port is null");
var routeRequest = new RouteRequest
{
Destination = destination,
DestinationPort = destinationPort,
Source = source,
SourcePort = sourcePort,
SignalType = signalType
};
var coolingDevice = destination as IWarmingCooling;
//We already have a route request for this device, and it's a cooling device and is cooling
if (RouteRequests.TryGetValue(destination.Key, out RouteRequest existingRouteRequest) && coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == true)
{
coolingDevice.IsCoolingDownFeedback.OutputChange -= existingRouteRequest.HandleCooldown;
coolingDevice.IsCoolingDownFeedback.OutputChange += routeRequest.HandleCooldown;
RouteRequests[destination.Key] = routeRequest;
Debug.LogMessage(LogEventLevel.Information, "Device: {destination} is cooling down and already has a routing request stored. Storing new route request to route to source key: {sourceKey}", null, destination.Key, routeRequest.Source.Key);
return;
}
//New Request
if (coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == true)
{
coolingDevice.IsCoolingDownFeedback.OutputChange += routeRequest.HandleCooldown;
RouteRequests.Add(destination.Key, routeRequest);
Debug.LogMessage(LogEventLevel.Information, "Device: {destination} is cooling down. Storing route request to route to source key: {sourceKey}", null, destination.Key, routeRequest.Source.Key);
return;
}
if (RouteRequests.ContainsKey(destination.Key) && coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == false)
{
var handledRequest = RouteRequests[destination.Key];
coolingDevice.IsCoolingDownFeedback.OutputChange -= handledRequest.HandleCooldown;
RouteRequests.Remove(destination.Key);
Debug.LogMessage(LogEventLevel.Information, "Device: {destination} is NOT cooling down. Removing stored route request and routing to source key: {sourceKey}", null, destination.Key, routeRequest.Source.Key);
}
destination.ReleaseRoute(destinationPort?.Key ?? string.Empty);
RunRouteRequest(routeRequest);
}
private static void RunRouteRequest(RouteRequest request)
{
try
{
if (request.Source == null)
return;
var (audioOrSingleRoute, videoRoute) = request.Destination.GetRouteToSource(request.Source, request.SignalType, request.DestinationPort, request.SourcePort);
if (audioOrSingleRoute == null && videoRoute == null)
return;
RouteDescriptorCollection.DefaultCollection.AddRouteDescriptor(audioOrSingleRoute);
if (videoRoute != null)
{
RouteDescriptorCollection.DefaultCollection.AddRouteDescriptor(videoRoute);
}
Debug.LogMessage(LogEventLevel.Verbose, "Executing full route", request.Destination);
audioOrSingleRoute.ExecuteRoutes();
videoRoute?.ExecuteRoutes();
} catch(Exception ex)
{
Debug.LogMessage(ex, "Exception Running Route Request {request}", null, request);
}
}
public static void ReleaseRoute(this IRoutingInputs destination)
{
ReleaseRoute(destination, string.Empty);
}
/// <summary>
/// Will release the existing route on the destination, if it is found in
/// RouteDescriptorCollection.DefaultCollection
/// </summary>
/// <param name="destination"></param>
public static void ReleaseRoute(this IRoutingInputs destination, string inputPortKey)
{
try
{
Debug.LogMessage(LogEventLevel.Information, "Release route for '{destination}':'{inputPortKey}'", destination?.Key ?? null, string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey);
if (RouteRequests.TryGetValue(destination.Key, out RouteRequest existingRequest) && destination is IWarmingCooling)
{
var coolingDevice = destination as IWarmingCooling;
coolingDevice.IsCoolingDownFeedback.OutputChange -= existingRequest.HandleCooldown;
}
RouteRequests.Remove(destination.Key);
var current = RouteDescriptorCollection.DefaultCollection.RemoveRouteDescriptor(destination, inputPortKey);
if (current != null)
{
Debug.LogMessage(LogEventLevel.Information, "Releasing current route: {0}", destination, current.Source.Key);
current.ReleaseRoutes();
}
} catch (Exception ex)
{
Debug.LogMessage(ex, "Exception releasing route for '{destination}':'{inputPortKey}'",null, destination?.Key ?? null, string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey);
}
}
/// <summary>
/// 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
@@ -234,6 +120,126 @@ namespace PepperDash.Essentials.Core
return (audioRouteDescriptor, videoRouteDescriptor);
}
private static void ReleaseAndMakeRoute(IRoutingInputs destination, IRoutingOutputs source, eRoutingSignalType signalType, RoutingInputPort destinationPort = null, RoutingOutputPort sourcePort = null)
{
if (destination == null) throw new ArgumentNullException(nameof(destination));
if (source == null) throw new ArgumentNullException(nameof(source));
if (destinationPort == null) Debug.LogMessage(LogEventLevel.Information, "Destination port is null");
if (sourcePort == null) Debug.LogMessage(LogEventLevel.Information, "Source port is null");
var routeRequest = new RouteRequest
{
Destination = destination,
DestinationPort = destinationPort,
Source = source,
SourcePort = sourcePort,
SignalType = signalType
};
var coolingDevice = destination as IWarmingCooling;
//We already have a route request for this device, and it's a cooling device and is cooling
if (RouteRequests.TryGetValue(destination.Key, out RouteRequest existingRouteRequest) && coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == true)
{
coolingDevice.IsCoolingDownFeedback.OutputChange -= existingRouteRequest.HandleCooldown;
coolingDevice.IsCoolingDownFeedback.OutputChange += routeRequest.HandleCooldown;
RouteRequests[destination.Key] = routeRequest;
Debug.LogMessage(LogEventLevel.Information, "Device: {destination} is cooling down and already has a routing request stored. Storing new route request to route to source key: {sourceKey}", null, destination.Key, routeRequest.Source.Key);
return;
}
//New Request
if (coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == true)
{
coolingDevice.IsCoolingDownFeedback.OutputChange += routeRequest.HandleCooldown;
RouteRequests.Add(destination.Key, routeRequest);
Debug.LogMessage(LogEventLevel.Information, "Device: {destination} is cooling down. Storing route request to route to source key: {sourceKey}", null, destination.Key, routeRequest.Source.Key);
return;
}
if (RouteRequests.ContainsKey(destination.Key) && coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == false)
{
var handledRequest = RouteRequests[destination.Key];
coolingDevice.IsCoolingDownFeedback.OutputChange -= handledRequest.HandleCooldown;
RouteRequests.Remove(destination.Key);
Debug.LogMessage(LogEventLevel.Information, "Device: {destination} is NOT cooling down. Removing stored route request and routing to source key: {sourceKey}", null, destination.Key, routeRequest.Source.Key);
}
routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination,destinationPort?.Key ?? string.Empty));
routeRequestQueue.Enqueue(new RouteRequestQueueItem(RunRouteRequest, routeRequest));
}
private static void RunRouteRequest(RouteRequest request)
{
try
{
if (request.Source == null)
return;
var (audioOrSingleRoute, videoRoute) = request.Destination.GetRouteToSource(request.Source, request.SignalType, request.DestinationPort, request.SourcePort);
if (audioOrSingleRoute == null && videoRoute == null)
return;
RouteDescriptorCollection.DefaultCollection.AddRouteDescriptor(audioOrSingleRoute);
if (videoRoute != null)
{
RouteDescriptorCollection.DefaultCollection.AddRouteDescriptor(videoRoute);
}
Debug.LogMessage(LogEventLevel.Verbose, "Executing full route", request.Destination);
audioOrSingleRoute.ExecuteRoutes();
videoRoute?.ExecuteRoutes();
} catch(Exception ex)
{
Debug.LogMessage(ex, "Exception Running Route Request {request}", null, request);
}
}
/// <summary>
/// Will release the existing route on the destination, if it is found in
/// RouteDescriptorCollection.DefaultCollection
/// </summary>
/// <param name="destination"></param>
private static void ReleaseRouteInternal(IRoutingInputs destination, string inputPortKey)
{
try
{
Debug.LogMessage(LogEventLevel.Information, "Release route for '{destination}':'{inputPortKey}'", destination?.Key ?? null, string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey);
if (RouteRequests.TryGetValue(destination.Key, out RouteRequest existingRequest) && destination is IWarmingCooling)
{
var coolingDevice = destination as IWarmingCooling;
coolingDevice.IsCoolingDownFeedback.OutputChange -= existingRequest.HandleCooldown;
}
RouteRequests.Remove(destination.Key);
var current = RouteDescriptorCollection.DefaultCollection.RemoveRouteDescriptor(destination, inputPortKey);
if (current != null)
{
Debug.LogMessage(LogEventLevel.Information, "Releasing current route: {0}", destination, current.Source.Key);
current.ReleaseRoutes();
}
} catch (Exception ex)
{
Debug.LogMessage(ex, "Exception releasing route for '{destination}':'{inputPortKey}'",null, destination?.Key ?? null, string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey);
}
}
/// <summary>
/// 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

View File

@@ -38,5 +38,10 @@ namespace PepperDash.Essentials.Core
Debug.LogMessage(ex, "Exception handling cooldown", Destination);
}
}
public override string ToString()
{
return $"Route {Source?.Key ?? "No Source Device"}:{SourcePort?.Key ?? "auto"} to {Destination?.Key ?? "No Destination Device"}:{DestinationPort?.Key ?? "auto"}";
}
}
}

View File

@@ -0,0 +1,45 @@
using PepperDash.Core;
using PepperDash.Essentials.Core.Queues;
using System;
using Serilog.Events;
namespace PepperDash.Essentials.Core.Routing
{
public class RouteRequestQueueItem : IQueueMessage
{
private readonly Action<RouteRequest> action;
private readonly RouteRequest routeRequest;
public RouteRequestQueueItem(Action<RouteRequest> routeAction, RouteRequest request)
{
action = routeAction;
routeRequest = request;
}
public void Dispatch()
{
Debug.LogMessage(LogEventLevel.Information, "Dispatching route request {routeRequest}", null, routeRequest);
action(routeRequest);
}
}
public class ReleaseRouteQueueItem : IQueueMessage
{
private readonly Action<IRoutingInputs, string> action;
private readonly IRoutingInputs destination;
private readonly string inputPortKey;
public ReleaseRouteQueueItem(Action<IRoutingInputs, string> action, IRoutingInputs destination, string inputPortKey)
{
this.action = action;
this.destination = destination;
this.inputPortKey = inputPortKey;
}
public void Dispatch()
{
Debug.LogMessage(LogEventLevel.Information, "Dispatching release route request for {destination}:{inputPortKey}", null, destination?.Key ?? "no destination", string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey);
action(destination, inputPortKey);
}
}
}

View File

@@ -30,6 +30,6 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Crestron.SimplSharp.SDK.ProgramLibrary" Version="2.20.66" />
<PackageReference Include="PepperDashCore" Version="2.0.0-alpha-424" />
<PackageReference Include="PepperDashCore" Version="2.0.0-alpha-451" />
</ItemGroup>
</Project>

View File

@@ -28,14 +28,14 @@ namespace PepperDash.Essentials.Devices.Common.SoftCodec
for(var i = 1; i <= props.OutputCount; i++)
{
var outputPort = new RoutingOutputPort($"{Key}-output{i}", eRoutingSignalType.AudioVideo, eRoutingPortConnectionType.Hdmi, null, this);
var outputPort = new RoutingOutputPort($"output{i}", eRoutingSignalType.AudioVideo, eRoutingPortConnectionType.Hdmi, null, this);
OutputPorts.Add(outputPort);
}
for(var i = 1; i<= props.ContentInputCount; i++)
{
var inputPort = new RoutingInputPort($"{Key}-contentInput{i}", eRoutingSignalType.AudioVideo, eRoutingPortConnectionType.Hdmi, $"contentInput{i}", this);
var inputPort = new RoutingInputPort($"contentInput{i}", eRoutingSignalType.AudioVideo, eRoutingPortConnectionType.Hdmi, $"contentInput{i}", this);
InputPorts.Add(inputPort);
}
@@ -47,7 +47,7 @@ namespace PepperDash.Essentials.Devices.Common.SoftCodec
for(var i = 1; i <=props.CameraInputCount; i++)
{
var cameraPort = new RoutingInputPort($"{Key}-cameraInput{i}", eRoutingSignalType.Video, eRoutingPortConnectionType.Hdmi, $"cameraInput{i}", this);
var cameraPort = new RoutingInputPort($"cameraInput{i}", eRoutingSignalType.Video, eRoutingPortConnectionType.Hdmi, $"cameraInput{i}", this);
InputPorts.Add(cameraPort);
}

View File

@@ -49,7 +49,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Crestron.SimplSharp.SDK.Program" Version="2.20.66" />
<PackageReference Include="PepperDashCore" Version="2.0.0-alpha-424" />
<PackageReference Include="PepperDashCore" Version="2.0.0-alpha-451" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PepperDash.Essentials.Core\PepperDash.Essentials.Core.csproj" />