summaryrefslogtreecommitdiff
path: root/src/eventloop.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/eventloop.rs')
-rw-r--r--src/eventloop.rs144
1 files changed, 144 insertions, 0 deletions
diff --git a/src/eventloop.rs b/src/eventloop.rs
new file mode 100644
index 0000000..2cf8ecd
--- /dev/null
+++ b/src/eventloop.rs
@@ -0,0 +1,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);
+ }
+ }
+ }
+ }
+}