3 个版本

0.1.2 2019年3月29日
0.1.1 2019年3月25日
0.1.0 2019年3月25日

#1028 in 并发

每月24次下载
movie_example 中使用

MIT/Apache

26KB

电影

一个actor/线程编排库

crates.io repo 文档

概述

  • 几乎没有样板代码 - 看示例
  • 与稳定编译器兼容,但需要2018版
  • 除了 std 外没有外部依赖
  • 基于枚举的MPSC通道通信
  • 默认情况下,一个actor = 一个线程
  • 默认情况下,actor只接受消息,不发送回复
    • 发送回复的解决方案现在还不是最优雅的,请参见下面的高级示例
  • 网络RPC应该是可能的,但超出了此crate的范围。如果你想这样做,可以使用 input_derivecustom_code 来派生 SerializeDeserialize
  • 两个过程宏 - 请参阅movie_derive
  • actor需要在模块/crate范围内定义
  • 目前错误消息不好,使用宏加手动字符串解析魔法
  • 如果在(稳定的)TokenStream::to_string()中有大的破坏性变化,宏可能会中断

示例

以下示例被测试为忽略的,因为据我所知,在文档测试中无法运行过程宏。它们也在 tests 目录中,在那里它们被测试。

安装

[dependencies]
"movie" = "0.1"

简单actor

use movie::actor;

actor! { SimplestActor } // completely useless

actor! {
    SimpleActor
        input: Ping,
        on_message:
            Ping => (),
}

#[test]
fn test_simple_actor() {
    use SimpleActor::{Actor, Input};
    // Create and spawn the actor
    let actor = Actor {}.start();

    actor.send(Input::Ping);
    actor.stop(); // Will block, waiting for actor.
}

高级示例

use movie::actor;

use std::sync::mpsc::Sender;
actor! {
    StreamParsingActor
        public_visibility: true,
        docs: /// Actor that parses video from V4L2 device
              /// It's very consistent - failed every time so far.
        input:
            ChangeSource(String),
            SendState,
        // By default, Input enum does not have any trait auto-implemented.
        input_derive: Debug, PartialEq,
        // Whitespace and comments are irrelevant.
        // It's also optional to end sections (attributes) with a comma, with
        // exception of code attributes (on_stop, on_init etc.), which should
        // not end with comma, but rather either with nothing or with a semicolon.
        data:
            pub device: String,
            pub state_tx: Sender<u64>,
        on_init:
            if self.device == "admin secret device" {
                panic!("No access right for admin secret device");
            }
            let mut lines_parsed = 0; // This variable will be exposed to on_message.
                                      // This is suboptimal, but it is the simplest
                                      // way to allow for thread-local variables (`data`
                                      // is sent between threads, so it couldn't be used
                                      // e.g. for GTK references)
        on_message:
            ChangeSource(name) => {
                self.device = name;
            },
            SendState => {
                self.state_tx.send(lines_parsed).unwrap();
            }
        tick_interval: 5, // Every 5ms, default = 100
        on_tick: // on_message have priority over on_tick
            lines_parsed += 1;
        on_stop: ()
        // custom_code must end with a semicolon
        custom_code:
            pub const DEFAULT_DEVICE: &'static str = "video0";
}

#[test]
fn test_stream_parsing_actor() {
    use StreamParsingActor::{Actor, Input, DEFAULT_DEVICE};

    use std::sync::mpsc::channel;
    let (tx, rx) = channel();
    let cfg = Actor {
        device: DEFAULT_DEVICE.to_string(),
        state_tx: tx,
    };
    // Spawn the actor, let on_init run
    let actor = cfg.start(); // returns StreamParsingActor::Handle

    use std::thread::sleep;
    use std::time::Duration;
    sleep(Duration::from_millis(100));

    // We can use auto-derived traits on Input
    actor.send(dbg!(Input::SendState));
    println!("Ticked {} times in 100ms", rx.recv().unwrap()); // 20

    actor.stop();
}

扩展示例

请参阅movie_example

actor属性

如果这些词后面跟着冒号,它们是受限关键字。

  • input - 定义 Input 枚举
  • input_derive - 为 Input 枚举提供 #[derive)]
  • data - actor 状态变量,需要在创建actor时设置
  • on_init - 在演员开始接收消息之前运行
  • on_message - 定义 match 消息 逻辑
  • tick_interval - 两次tick之间的时间(毫秒)。当未定义时,设置为100ms。影响消息轮询,因此不要设置得太高。
  • on_tick - 每次tick时运行
  • on_stop - 在演员停止接收消息之后运行
  • spawner - 创建线程的函数名称(默认为 std::thread::spawn,在此处放置一个具有相似签名的函数,以便将演员作为future、M:N线程等运行)
  • spawner_return_type - spawner 的返回类型(默认为 std::thread::JoinHandle<()>
  • custom_code - 要插入到生成的演员模块中的代码
  • public_visibility - 如果 true,则演员模块是公开的
  • docs - 在此处放置文档 - 例如 docs: /// 一个演员

某些代码可能会破坏宏的内部(例如,没有定义自己的循环就使用 breakcontinue),这可能会破坏演员的主循环,放置 on_stop: (), 将导致无效逗号。调试它可能会很困难,希望 actor_dbg(当代码无法编译时)和 cargo-expand(当代码可以编译时)将有助于您在这些情况下。

历史

以前,我写过 x11-input-supercharger,这是一个用于自动滚动和条件鼠标到键盘重绑定的实用程序(不改变键映射本身,这会导致基于Chromium的应用程序冻结一秒钟)。它运行着许多线程 - 一个用于自动滚动,其他用于重绑定,另一个用于轮询X11事件,还有一个用于显示带有类似Windows自动滚动状态指示器的GTK3窗口。代码并不复杂,消息层次结构也很简单(父消息传递给子消息,而不是相反),但样板代码的增加成为了代码可读性的很大障碍,尤其是在线程还需要执行自己的工作,而不仅仅是响应消息的情况下(工作包括与X11、GTK3交互以及跟踪时间)。而不是使用 actix,我决定尝试实现自己的库,受到 actress 的启发,并花费了大约12个小时(我远非熟练的Rust程序员,这也是我第一次使用过程宏)。

这是我第一个“真正的”库(我之前的项目/Rust程序是CLI/GUI工具,有时带有简单的公共Rust API)。我发布了一些到crates.io,但不是全部(一些在GitHub上,一些尚未发布)。我了解到了过程宏(主要关于它们当前的不足)——遗憾的是,关于它们的资料并不多。我主要依赖于Rust参考手册。出人意料的是,我也无法快速找到有关文档的正确方法(带有链接和示例)——所以我使用了Rust书籍的第一版。我可能最终应该阅读第二版,以便熟悉里面的内容以及它们的位置(在我开始的时候,还没有第二版)。

野外的使用

如果你在项目中使用这个库,请考虑将其放在这里(如果可能的话)。这将帮助我测试我引入的新更改是否会破坏任何东西。

常见问题解答

请参阅Reddit上的原始公告

文档

README.md使用cargo-readme构建(cargo readme > README.md)。文档由docs.sh脚本构建。

许可证

版权所有2019年Paweł Zmarzły。根据您的选择,许可方式为:

任意选择。

贡献

除非您明确声明,否则根据Apache-2.0许可证定义的,您有意提交以包含在作品中的任何贡献,都将按上述方式双许可,没有任何附加条款或条件。

许可证:MIT OR Apache-2.0

依赖项