aatxe / irc

the irc crate – usable, async IRC for Rust
Mozilla Public License 2.0
528 stars 97 forks source link

Tweeter Example with new API #138

Closed fmckeogh closed 6 years ago

fmckeogh commented 6 years ago

Hello,

I'm really sorry if this is not the right place for this but I'm having a little trouble with the new API.

I'd like to write a program that spawns a thread per client message handler and another thread that sends a message every n seconds, how would I go about doing this?

Thanks :)

aatxe commented 6 years ago

As it currently stands, you can only have a single message handler per client. That is, for each IrcClient you have, you can only call IrcClient::stream(...) once (this is documented there). So, depending on precisely what you meant by "spawning a thread per client message handler," it unfortunately might not be possible. I do have some vague ideas about how we might be able to alleviate this restriction in future versions, but I haven't worked them out yet.

With that out of the way, there should be two ways to implement the tweeter example with the new API. One is a fairly direct port, and the other is relying more heavily on other Rust async libraries. In my personal opinion, the latter is much more pleasant code to read and write.

Porting tweeter.rs to the new API

extern crate irc;

use std::default::Default;
use std::thread;
use std::time::Duration;
use irc::client::prelude::*;

fn main() {
    let config = Config {
        nickname: Some("pickles".to_owned()),
        server: Some("irc.fyrechat.net".to_owned()),
        channels: Some(vec!["#irc-crate".to_owned()]),
        ..Default::default()
    };
    // We need to create a reactor first and foremost
    let mut reactor = IrcReactor::new().unwrap();
    // and then create a client via its API.
    let client = reactor.prepare_client_and_connect(&config).unwrap();
    // Then, we identify
    client.identify().unwrap();
    // and clone just as before.
    let send_client = client.clone();

    // Rather than spawn a thread that reads the messages separately, we register a handler with the
    // reactor. just as in the original version, we don't do any real handling and instead just print
    // the messages that are received.
    reactor.register_client_with_handler(client, |_, message| {
        print!("{}", message);
        Ok(())
    });

    // Meanwhile, we need to run the reactor on the thread it was created on (it is not `Send`). So,
    // we actually move the sending part (which was previously on the main thread) to the new thread.
    thread::spawn(move || {
        // But the implementation for sending is entirely the same.
        loop {
            send_client.send_privmsg("#irc-crate", "TWEET TWEET").unwrap();
            thread::sleep(Duration::new(10, 0));
        }
    });

    // Then, on the main thread, we finally run the reactor which blocks the program indefinitely.
    reactor.run().unwrap();
}

Using tokio-timer and a single thread

The alternative is to actually keep the entire program on one thread and actually run the sending thread as a future on the IrcReactor. You can do this using tokio-timer and IrcReactor::register_future(...). To do so, we can replace the newly spawned thread with the following code (and an extern crate tokio_timer):

    // We construct an interval using a wheel timer from tokio_timer. This interval will fire
    // every 10 seconds (and is accurate to the second).
    let send_interval = tokio_timer::wheel()
        .tick_duration(Duration::from_secs(1))
        .num_slots(256)
        .build()
        .interval(Duration::from_secs(10));

    // And then spawn a new future that performs the given action each time it fires.
    reactor.register_future(send_interval.map_err(IrcError::Timer).for_each(move |()| {
        // Anything in here will happen every 10 seconds!
        send_client.send_privmsg("#irc-crate", "TWEET TWEET")
    }));
fmckeogh commented 6 years ago

Thank you so much for your detailed response! :)

I wasn't to clear myself on the multithreaded model, tokio-timer is definitely a lot cleaner and does exactly what I need. Thanks again! :)

aatxe commented 6 years ago

Happy to have helped! 😄

I've also added tooter.rs as an example in a23f3417f18bd150baa9634da7ae16a76a5f96ad which uses the "fully async" style with tokio-timer.