starina_api::mainloop

Struct Mainloop

Source
pub struct Mainloop<Ctx, AllM> { /* private fields */ }
Expand description

The mainloop for applications.

This is a simple event loop to enable asynchronous programming without Rust’s async fns. It is designed to be used in the main function of applications.

§Per-object state

Each object in the mainloop has its own state. For example, a TCP socket channel would have a state to track the connection state, timers, and TX/RX buffers.

See the example below for how to define and use per-object state.

§Why not async Rust?

This API is very similar to epoll + non-blocking I/O in Linux. An event loop API like this means that you need to write state machines manually, which async Rust (async fn) does automatically.

However, explicit state machines make debugging easier because the execution flow is crystal clear. Also we don’t have to care about pitfalls like cancellation safety. Moreover, my observation is that most of OS components are very simple and manual state machines are sufficient.

§Future work

  • Support multi-threaded mainloop.

§Example

starina_api::autogen!(); // Include starina_autogen module

use starina_api::channel::Channel;
use starina_api::environ::Environ;
use starina_api::mainloop::Event;
use starina_api::mainloop::Mainloop;
use starina_api::prelude::*;
use starina_autogen::idl::ping::PingReply;
use starina_autogen::idl::Message;

// Per-object state.
#[derive(Debug)]
enum Context {
    Startup,
    Client { counter: i32 },
}

#[no_mangle]
pub fn main(mut env: Environ) {
    let mut mainloop = Mainloop::<Context, Message>::new().unwrap();

    // Take the startup channel, and start receiving messages through the
    // mainloop.
    let startup_ch = env.take_channel("dep:startup").unwrap();
    mainloop.add_channel(startup_ch, Context::Startup).unwrap();

    // Mainloop!
    loop {
       // Wait for the next event. Use `match` not to miss unexpected cases.
        match mainloop.next() {
            Event::Message { // The "message received" event.
                ctx: Context::Startup, // The message is from startup.
                message: Message::NewClient(m), // NewClient message.
                ..
            } => {
                // Take the new client's channel and register it to the
                // mainloop.
                let new_ch = m.handle.take::<Channel>().unwrap();
                mainloop
                    .add_channel(new_ch, Context::Client { counter: 0 })
                    .unwrap();
            }
            Event::Message { // The "message received" event.
                ctx: Context::Client { counter }, // The message is from a client.
                message: Message::Ping(m), // Ping message.
                sender, // The channel which received the message.
            } => {
                // Update the per-object state. It's mutable!
                *counter += 1;

                // Reply with the counter value.
                if let Err(err) = sender.send(PingReply { value: *counter }) {
                    debug_warn!("failed to reply: {:?}", err);
                }
            }
            ev => {
                panic!("unexpected event: {:?}", ev);
            }
        }
    }
}

Implementations§

Source§

impl<Ctx, AllM: MessageDeserialize> Mainloop<Ctx, AllM>

Source

pub fn new() -> Result<Self, Error>

Creates a new mainloop.

Source

pub fn remove(&mut self, handle_id: HandleId) -> Result<(), FtlError>

Removes an object.

Source

pub fn add_channel<T: Into<(ChannelSender, ChannelReceiver)>>( &mut self, channel: T, state: Ctx, ) -> Result<(), Error>

Adds a channel to start receiving messages in the mainloop.

Source

pub fn add_interrupt( &mut self, interrupt: Interrupt, state: Ctx, ) -> Result<(), Error>

Adds an interrupt to start receiving interrupts in the mainloop.

Source

pub fn next(&mut self) -> Event<'_, Ctx, AllM>

Waits for the next event. Blocks until an event is available.

Auto Trait Implementations§

§

impl<Ctx, AllM> Freeze for Mainloop<Ctx, AllM>

§

impl<Ctx, AllM> RefUnwindSafe for Mainloop<Ctx, AllM>
where AllM: RefUnwindSafe, Ctx: RefUnwindSafe,

§

impl<Ctx, AllM> Send for Mainloop<Ctx, AllM>
where AllM: Send, Ctx: Send,

§

impl<Ctx, AllM> Sync for Mainloop<Ctx, AllM>
where AllM: Sync, Ctx: Sync,

§

impl<Ctx, AllM> Unpin for Mainloop<Ctx, AllM>
where AllM: Unpin, Ctx: Unpin,

§

impl<Ctx, AllM> UnwindSafe for Mainloop<Ctx, AllM>
where AllM: UnwindSafe, Ctx: UnwindSafe,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.