我如何将 discord-rs 事件与来自 Twitter 或计时器的其他事件结合起来?

How can I combine discord-rs events with other events from Twitter or timers?

我使用 discord-rs 库为 Discord 聊天服务编写了一个机器人。这个库在主循环中的单个线程中出现时会给我事件:

fn start() {
    // ...
    loop {
        let event = match connection.recv_event() {
            Ok(event) => event,
            Err(err) => { ... },
        }
    }
}

我想添加一些计时器和其他在它们自己的线程中计算的东西,它们必须通知我在主循环的线程中做一些事情。我还想添加 Twitter 支持。所以它可能看起来像这样:

(Discord's network connection, Twitter network connection, some timer in another thread) -> main loop

这看起来像这样:

fn start() {
    // ...
    loop {
        let event = match recv_events() {
            // 1. if Discord - do something with discord
            // 2. if timer - handle timer's notification
            // 3. if Twitter network connection - handle twitter
        }
    }
}

在原始的 C 和 C 套接字中,它可以通过(e)轮询它们来完成,但在这里我不知道如何在 Rust 中做到这一点,或者它是否可能。我想我想要类似 poll 的一些不同来源的东西,它们会为我提供不同类型的对象。

我想如果我为 mio 的 Evented 特性提供包装器并按照 Deadline 示例中所述使用 mio 的投票,我想这可以实现。

有什么方法可以将这些事件结合起来吗?

在你的情况下,我会为你需要的每个服务启动一个线程,然后使用 mpsc 通道将事件发送到主循环。

示例:

use std::thread;
use std::sync::mpsc::channel;

enum Event {
    Discord(()),
    Twitter(()),
    Timer(()),
}

fn main() {
    let (tx, rx) = channel();
    // discord
    let txprime = tx.clone();
    thread::spawn(move || {
        loop {
            // discord loop
            txprime.send(Event::Discord(())).unwrap()
        }
    });

    // twitter
    let txprime = tx.clone();
    thread::spawn(move || {
        loop {
            // twitter loop
            txprime.send(Event::Twitter(())).unwrap()
        }
    });

    // timer
    let txprime = tx.clone();
    thread::spawn(move || {
        loop {
            // timer loop
            txprime.send(Event::Timer(())).unwrap()
        }
    });

    // Main loop
    loop {
        match rx.recv().unwrap() {
            Event::Discord(d) => unimplemented!(),
            Event::Twitter(t) => unimplemented!(),
            Event::Timer(t)   => unimplemented!(),
        }
    }
}

This library gives me events when they arise in a single thread in a main loop

"single thread" 这件事只适用于小型机器人。一旦您到达 2500 guilds limit,Discord 将拒绝以正常方式连接您的机器人。你必须使用分片。我 猜想 你不会为你的 bot 分片提供新的虚拟服务器。机会是,您将生成新的 线程 ,每个分片一个事件循环。

这是我的做法,顺便说一句:

fn event_loop(shard_id: u8, total_shards: u8){
    loop {
        let bot = Discord::from_bot_token("...").expect("!from_bot_token");
        let (mut dc, ready_ev) = bot.connect_sharded(shard_id, total_shards).expect("!connect");
        // ...
    }
}

fn main() {
    let total_shards = 10;
    for shard_id in 0..total_shards {
        sleep(Duration::from_secs(6)); // There must be a five-second pause between connections from one IP.
        ThreadBuilder::new().name (fomat! ("shard " (shard_id)))
          .spawn (move || {
            loop {
              if let Err (err) = catch_unwind (move || event_loop (shard_id, total_shards)) {
                log! ("shard " (shard_id) " panic: " (gstuff::any_to_str (&*err) .unwrap_or ("")));
                sleep (Duration::from_secs (10));
                continue}  // Panic restarts the shard.
              break}
          }) .expect ("!spawn");
    }
}

I want to add some timers and other things which are calculated in their own threads and which must notify me to do something in the main loop's thread

选项 1。不要。

很有可能,您真的不需要返回 Discord 事件循环!假设您想 post 回复,更新嵌入等。您不需要 Discord 事件循环来做到这一点!

Discord API 分为两部分:
1) Websocket API,由 Connection 表示,用于从 Discord 获取 事件。
2) REST API,由Discord接口表示,用于发送事件。

您几乎可以从任何地方发送事件。从任何线程。甚至可能来自您的计时器。

DiscordSync。将其包装在 Arc 中并与您的计时器和线程共享。

方案2.抓住机会。

即使 recv_event 没有超时,Discord 也会不断向您发送新事件。用户正在登录、退出、打字、post发送消息、开始视频游戏、编辑内容等等。实际上,如果事件流停止,那么您的 Discord 连接就会出现问题(对于我的机器人,我已经根据该信号实施了高可用性故障转移)。

您可以与线程和计时器共享 deque。一旦计时器结束,它将 post 向双端队列发送一些数据,然后偶数循环将检查双端队列是否有新的事情要做,一旦 Discord 用新事件唤醒它。

选项3.鸟瞰图。

正如 belst 所指出的,您可以启动一个通用事件循环,一个循环 "to rule them all",然后将 Discord 事件提升到该循环中。这特别有趣,因为通过分片,您将拥有多个事件循环。

因此,Discord 事件循环 -> 简单事件过滤器 -> 频道 -> 主事件循环。

选项 4. 分片。

如果您希望您的机器人在代码升级和重新启动期间保持在线,那么您应该提供一种方法来分别重新启动每个分片(或者以其他方式在分片级别实施高可用性故障转移,就像我所做的那样)。因为您无法在进程重启后立即连接所有分片,所以 Discord 不会让您这样做。

如果您的所有分片共享同一个进程,那么在该进程重新启动后,您必须等待五秒钟才能附加新的分片。使用 10 个分片,机器人停机时间将近一分钟。

分离分片重启的一种方法是为每个分片分配一个进程。然后,当您需要升级机器人时,您将分别重新启动每个进程。这样 仍然需要为每个分片等待五到六秒,但是 你的用户 不需要。

更好的是,您现在只需要为 discord-rs 升级和类似的维护相关任务重新启动 Discord 事件循环进程。另一方面,您的主事件循环可以立即重新启动,并且可以任意频繁地重新启动。这应该大大加快编译-运行-测试循环。

因此,Discord 事件循环,在单独的分片进程中 -> 简单事件过滤器 -> RPC 或数据库 -> 主事件循环,在单独的进程中。