1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
use super::*;

/// A result of racing two routines
///
/// This is generated by race, and allows you to
/// continue with the 'losing' coroutine, and
/// examing the result of the 'winning' courtine
pub enum RaceResult<'a, I, O, A, B> {
    Left {
        value: A,
        remaining: Coroutine<'a, I, O, B>,
    },
    Right {
        value: B,
        remaining: Coroutine<'a, I, O, A>,
    },
}

/// Run both coroutines, and returns which finishes first
/// For each step this feeds inputs to left first, then right
/// so if they are equal, left will return first
/// Raceresult returns the value, and the remaining coroutine, so it
/// can be ran elsewhere
///
/// Input must be clone-able, as it needs to be consumed by both routines
pub fn race<'a, I, O, A, B>(
    a: Coroutine<'a, I, O, A>,
    b: Coroutine<'a, I, O, B>,
) -> Coroutine<'a, I, O, RaceResult<'a, I, O, A, B>>
where
    I: Clone,
{
    let sr1 = run_step(a);
    let sr2 = run_step(b);
    match (sr1, sr2) {
        (StepResult::Done(value), StepResult::Done(b)) => {
            let ret = RaceResult::Left {
                value,
                remaining: result(b),
            };
            result(ret)
        }
        (StepResult::Done(value), StepResult::Yield { output, next }) => {
            let remaining = *next;
            let race = RaceResult::Left { value, remaining };
            right(send(output), result(race))
        }
        (StepResult::Done(value), StepResult::Next(next)) => {
            let remaining = suspend(next);
            result(RaceResult::Left { value, remaining })
        }
        (StepResult::Yield { output, next }, StepResult::Done(value)) => {
            let remaining = *next;
            let race = RaceResult::Right { value, remaining };
            right(send(output), result(race))
        }
        (
            StepResult::Yield {
                output: a,
                next: na,
            },
            StepResult::Yield {
                output: b,
                next: nb,
            },
        ) => {
            let send = tuple(send(a), send(b));
            let next = race(*na, *nb);
            right(send, next)
        }
        (StepResult::Yield { output, next: a }, StepResult::Next(b)) => {
            let send = send(output);
            let next = race(*a, suspend(b));
            right(send, next)
        }
        (StepResult::Next(a), StepResult::Done(value)) => {
            let remaining = suspend(a);
            let race = RaceResult::Right { value, remaining };
            result(race)
        }
        (StepResult::Next(a), StepResult::Yield { output, next }) => {
            let send = send(output);
            let a = suspend(a);
            let b = *next;
            let next = race(a, b);
            right(send, next)
        }
        (StepResult::Next(a), StepResult::Next(b)) => {
            let on_input = |input: I| {
                let a = a(input.clone());
                let b = b(input);
                race(a, b)
            };
            bind(receive(), on_input)
        }
    }
}

/// Sends inputs to both coroutines, and will emit outputs together
/// If one finishes first, the other will consume the inputs until it is finished.
/// Both routines must finish to return
/// Input must be copyable, as it will need to feed both
pub fn broadcast<'a, I, O, A, B>(
    first: Coroutine<'a, I, O, A>,
    second: Coroutine<'a, I, O, B>,
) -> Coroutine<'a, I, O, (A, B)>
where
    I: Clone,
{
    let rr = race(first, second);
    let on_result = |res| match res {
        RaceResult::Left { value, remaining } => map(remaining, |b| (value, b)),
        RaceResult::Right { value, remaining } => map(remaining, |a| (a, value)),
    };
    bind(rr, on_result)
}