如何将 discord-rs 事件与来自 Twitter 或计时器的其他事件结合起来?
Posted
技术标签:
【中文标题】如何将 discord-rs 事件与来自 Twitter 或计时器的其他事件结合起来?【英文标题】:How can I combine discord-rs events with other events from Twitter or timers? 【发布时间】:2017-04-26 11:39:40 【问题描述】:我使用 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 的民意调查,这可以实现。
有没有办法把这些事件结合起来?
【问题讨论】:
【参考方案1】:当它们出现在主循环的单个线程中时,这个库会为我提供事件
“单线程”仅适用于小型机器人。一旦您到达2500 guilds limit,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
接口表示,用于发送事件。
您几乎可以从任何地方发送事件。从任何线程。甚至可能来自您的计时器。
Discord
是 Sync
。将其包装在 Arc
中并与您的计时器和线程共享。
选项 2. 抓住机会。
即使recv_event
没有超时,Discord 也会不断向您发送新事件。用户正在登录、退出、打字、发布消息、启动视频游戏、编辑内容等等。实际上,如果事件流停止,那么您的 Discord 连接就会出现问题(对于我的机器人,我已经根据该信号实现了高可用性故障转移)。
您可以与您的线程和计时器共享deque。一旦计时器完成,它将向双端队列发布一些内容,然后一旦 Discord 用新事件唤醒它,偶数循环将检查双端队列是否有新的事情要做。
选项 3. 鸟瞰图。
正如belst 所指出的,您可以启动一个通用事件循环,一个“统治所有事件”的循环,然后将 Discord 事件提升到该循环中。这特别有趣,因为通过分片,您将拥有多个事件循环。
所以,Discord 事件循环 -> 简单事件过滤器 -> 通道 -> 主事件循环。
选项 4。分片。
如果您希望您的机器人在代码升级和重新启动期间保持在线,那么您应该提供一种方法来分别重新启动每个分片(或者像我一样在分片级别实施高可用性故障转移)。因为您无法在进程重新启动后立即连接所有分片,所以 Discord 不会让您这样做。
如果您的所有分片共享同一个进程,则在该进程重新启动后,您必须等待 5 秒钟才能附加新分片。使用 10 个分片,机器人停机时间几乎是一分钟。
分离分片重启的一种方法是为每个分片分配一个进程。然后,当您需要升级机器人时,您将分别重新启动每个进程。这样你仍然需要等待每个分片五到六秒,但你的用户的不需要。
更好的是,您现在只需为 discord-rs 升级和类似的维护相关任务重新启动 Discord 事件循环进程。另一方面,您的主事件循环可以立即重新启动,并且可以根据需要随时重新启动。这应该会大大加快编译-运行-测试循环。
所以,Discord 事件循环,在一个单独的分片进程中 -> 简单事件过滤器 -> RPC 或数据库 -> 主事件循环,在一个单独的进程中。
【讨论】:
【参考方案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!(),
【讨论】:
以上是关于如何将 discord-rs 事件与来自 Twitter 或计时器的其他事件结合起来?的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 Twit NPM 包获取 Twitter 用户名和头像