/* 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+From>(s: &mut Slab, 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 for MToken { fn from(val: usize) -> MToken { MToken(val) } } impl From for usize { fn from(val: MToken) -> usize { val.0 } } pub struct EventLoop { poll: Poll, tokens: Slab, // A machine entry is set to None during a method call on the Machine object. machines: Slab>, 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) } 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(&mut self, io: &E, token: Token, interest: mio::Ready) where E: mio::Evented { self.parent.poll.register(io, token, interest, mio::PollOpt::level()).unwrap(); } pub fn reregister(&mut self, io: &E, token: Token, interest: mio::Ready) where E: mio::Evented { self.parent.poll.reregister(io, token, interest, mio::PollOpt::level()).unwrap(); } pub fn deregister(&mut self, io: &E) where E: mio::Evented { self.parent.poll.deregister(io).unwrap(); } pub fn remove(&mut self) { self.removed = true; } pub fn is_removed(&self) -> bool { self.removed } pub fn spawn(&mut self, machine: Box) { 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) { let mtoken = slab_insert(&mut self.machines, None); trace!("Spawning machine {}", mtoken.0); { 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(); for event in events.iter() { let mtoken = self.tokens[event.token()]; trace!("Poll returned event {} on machine {} state {:?}", event.token().0, mtoken.0, event.kind()); 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); } else { trace!("Removing machine {}", mtoken.0); } } } } }