0

我使用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 的 trait 提供一个包装器并按照示例Evented中的描述使用 mio 的民意调查,这可以实现。Deadline

有没有办法结合这些事件?

4

2 回答 2

2

当它们出现在主循环中的单个线程中时,该库会为我提供事件

“单线程”仅适用于小型机器人。一旦达到2500 行会限制,Discord 将拒绝以正常方式连接您的机器人。你必须使用分片。而且我你不会为你的机器人分片配置新的虚拟服务器。机会是,您将生成新线程,每个分片一个事件循环。

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

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");
    }
}

我想添加一些计时器和其他在他们自己的线程中计算的东西,它们必须通知我在主循环的线程中做某事

选项 1. 不要。

机会是,你真的不需要回到Discord 事件循环!假设您要发布回复、更新嵌入等。您不需要 Discord 事件循环来执行此操作!

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

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

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

选项 2. 抓住机会。

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

您可以与您的线程和计时器共享一个双端队列。一旦计时器完成,它将向双端队列发布一些东西,然后一旦 Discord 用新事件唤醒它,偶数循环将检查双端队列是否有新的事情要做。

选项 3. 鸟瞰图。

正如belst指出的那样,您可以启动一个通用事件循环,一个“统治所有事件”的循环,然后将 Discord 事件提升到该循环中。这特别有趣,因为使用分片,您将拥有多个事件循环。

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

选项 4. 分片。

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

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

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

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

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

于 2017-04-26T19:47:37.017 回答
2

在您的情况下,我将为您需要的每个服务启动一个线程,然后使用 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!(),
        }
    }
}
于 2017-04-26T14:23:56.300 回答