How to Detect Concurrent Events Using LINQ and Reactive Extensions
Here's the scenario...
I have 3 referees around a powerlifting platform. Each has a set of 4 switches in their hand that is used to indicate if they thought a lift was good or not. One switch indicates the lift was good and the other 3 indicate the lift was no good and the general reason why. A referee's light box displays the result by turning lights on and off. The individual lights should only turn on when all 3 referees have pushed their buttons. How do I determine when all 3 referees have pushed their buttons?
To get started, we need to create a simple event to indicate a referees decision.
public class RefereeDecisionEvent
{
public DateTimeOffset Timestamp { get; set; }
public DecisionCode Decision { get; set; }
}
The Decision property will hold a referee's decision represented as a DecisionCode enumeration.
public enum DecisionCode
{
White = 0,
Red = 1,
Blue = 2,
Yellow = 4
}
White indicates the referee decided it was a good lift. Red, Blue, and Yellow represent potential reasons why the referee decided the lift was no good.
We'll also need 3 event sources. Each event source represents a different referee; left, center, right. The actual implementation of the sources is outside the scope of this blog post. That said, I'll just generate some events for each source at different intervals to give us some simple test data.
Step 1 is to join the left source's events with the right source's events. Since the left and right event sources create new events at different intervals, the events will most likely not occur at exactly the same time. We can fix this by increasing the duration of both sequences of events by 3 seconds. Now, we are joining the events from the left and the right sources when they occur within 3 seconds of each other.
var step1 = leftSource.Join(rightSource,
l => Observable.Interval(TimeSpan.FromSeconds(3)),
r => Observable.Interval(TimeSpan.FromSeconds(3)),
(l, r) => new {Left = l, Right = r});
Step 2 is to join the center source's events with the now joined left and right side events. We'll extend the durations of the events 3 seconds so that we can get a combined event that shows the left, center, and right events that all occurred withing 3 seconds of each other.
var step2 = centerSource.Join(step1,
l => Observable.Interval(TimeSpan.FromSeconds(3)),
r => Observable.Interval(TimeSpan.FromSeconds(3)),
(l, r) => new {r.Left, Center = l, r.Right});
In Step 3, we'll project the results of step 2 into an event that is better laid out for our purposes. That Observable can then be subscribed to by an Observer that handles turning the lights on and off.
var step3 = from e in step2
select new
{
e.Left,
e.Center,
e.Right,
Status = new[] {e.Left.Decision, e.Center.Decision, e.Right.Decision}.Count(x => x == DecisionCode.White) >= 2 ? "Good" : "Bad"
};
And that's how it's done.