summaryrefslogtreecommitdiff
path: root/src/eventloop.rs
blob: 2cf8ecd0bc7d120674c0a2998ee9f1149301a7db (plain)
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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
/* This is a thin wrapper around mio for building state machines on a shared event loop.
 * It's similar in spirit to Rotor, but with a bunch of simplifications and other differences.
 * (Rotor is currently in too much flux and its ecosystem very incomplete/alpha)
 *
 * The biggest difference (I think) between Rotor and this is the way it deals with the problem of
 * communicating actions back from the Machine to the EventLoop. Since the EventLoop is the owner
 * of the Machine, it is not possible to give a mutable borrow of the EventLoop to a method in the
 * Machine, as that what will result in a double mutable borrow. This problem is inherent in the
 * architecture and Rust is correct to disallow the borrow; since it would be possible to mutate
 * the Machine object through multiple aliases.
 *
 * Rotor solves this problem by having the method handlers return a "response", i.e. an action that
 * it wants the EventLoop to perform. This implementation is more flexible but slightly more hacky:
 * When calling a method in the Machine, the Machine object itself is temporarily removed from the
 * EventLoop object, thus removing the alias and allowing both objects to be Mutably borrowed.
 * Downside: This solution is more prone to logic errors that Rust can't catch, like removing or
 * re-using the MToken while inside a Machine handler.
 *
 * TODO: Machines don't care on which thread they run, so as a scalability improvement it's
 * possible to spawn a configurable number of threads on start-up, run a separate event loop on
 * each, and assign each Machine to a different event loop.
 */
use mio;
use mio::{Poll,Events,Token};
use slab::Slab;


// TODO: Upstream this to Slab crate?
fn slab_insert<T, I:Into<usize>+From<usize>>(s: &mut Slab<T,I>, t: T) -> I {
    s.insert(t).or_else(|t| {
        // Grow by some small fixed number. From what I can see the Slab implementation already
        // does exponential growth internally.
        s.reserve_exact(8);
        s.insert(t)
    }).unwrap_or_else(|_| { unreachable!() })
}


pub trait Machine {
    fn init(&mut self, &mut Context);
    fn handle(&mut self, &mut Context, mio::Event);
}


#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct MToken(pub usize);
impl From<usize> for MToken { fn from(val: usize) -> MToken { MToken(val) } }
impl From<MToken> for usize { fn from(val: MToken) -> usize { val.0 } }


pub struct EventLoop {
    poll: Poll,
    tokens: Slab<MToken, Token>,
    // A machine entry is set to None during a method call on the Machine object.
    machines: Slab<Option<Box<Machine>>, MToken>
}

pub struct Context<'a> {
    parent: &'a mut EventLoop,
    machine: MToken,
    removed: bool
}


impl<'a> Context<'a> {
    pub fn assign(&mut self) -> Token {
        slab_insert(&mut self.parent.tokens, self.machine)
    }

    #[allow(dead_code)]
    pub fn unassign(&mut self, t: Token) {
        assert_eq!(self.parent.tokens[t], self.machine);
        self.parent.tokens.remove(t);
    }

    // This method is opinionated in always providing a PollOpt::level(). It's the only option that
    // makes sense. :)
    pub fn register<E: ?Sized>(&mut self, io: &E, token: Token, interest: mio::Ready) where E: mio::Evented {
        self.parent.poll.register(io, token, interest, mio::PollOpt::level()).unwrap();
    }

    #[allow(dead_code)]
    pub fn reregister<E: ?Sized>(&mut self, io: &E, token: Token, interest: mio::Ready) where E: mio::Evented {
        self.parent.poll.reregister(io, token, interest, mio::PollOpt::level()).unwrap();
    }

    #[allow(dead_code)]
    pub fn deregister<E: ?Sized>(&mut self, io: &E) where E: mio::Evented {
        self.parent.poll.deregister(io).unwrap();
    }

    #[allow(dead_code)]
    pub fn remove(&mut self) {
        self.removed = true;
    }

    #[allow(dead_code)]
    pub fn spawn(&mut self, machine: Box<Machine>) {
        self.parent.spawn(machine);
    }
}


impl EventLoop {
    pub fn new() -> EventLoop {
        EventLoop {
            poll: Poll::new().unwrap(),
            tokens: Slab::with_capacity(16),
            machines: Slab::with_capacity(32),
        }
    }

    pub fn spawn(&mut self, mut machine: Box<Machine>) {
        let mtoken = slab_insert(&mut self.machines, None);
        {
            let mut ctx = Context{ parent: self, machine: mtoken, removed: false };
            machine.init(&mut ctx);
            assert!(!ctx.removed);
        }
        self.machines[mtoken] = Some(machine);
    }

    pub fn run(&mut self) {
        let mut events = Events::with_capacity(64);
        debug!("Entering event loop");
        loop {
            self.poll.poll(&mut events, None).unwrap();
            trace!("Poll returned with {} events", events.len());
            for event in events.iter() {
                let mtoken = self.tokens[event.token()];
                let mut machine = self.machines.entry(mtoken).unwrap().replace(None).unwrap();

                let removed = {
                    let mut ctx = Context{ parent: self, machine: mtoken, removed: false };
                    machine.handle(&mut ctx, event);
                    ctx.removed
                };

                if !removed {
                    self.machines[mtoken] = Some(machine);
                }
            }
        }
    }
}