movie

An actor/thread orchestration library

Overview

Examples

The examples below are test-ignored as AFAIK it's impossible to run procedural macros in doc-tests. They are also in tests directory, where they are tested.

Installation

toml [dependencies] "movie" = "0.1"

Simple actor

```rust use movie::actor;

actor! { SimplestActor } // completely useless

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

[test]

fn testsimpleactor() { use SimpleActor::{Actor, Input}; // Create and spawn the actor let actor = Actor {}.start();

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

} ```

Advanced example

```rust use movie::actor;

use std::sync::mpsc::Sender; actor! { StreamParsingActor publicvisibility: 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. inputderive: Debug, PartialEq, // Whitespace and comments are irrelevant. // It's also optional to end sections (attributes) with a comma, with // exception of code attributes (onstop, oninit etc.), which should // not end with comma, but rather either with nothing or with a semicolon. data: pub device: String, pub statetx: Sender, oninit: if self.device == "admin secret device" { panic!("No access right for admin secret device"); } let mut linesparsed = 0; // This variable will be exposed to onmessage. // 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) onmessage: ChangeSource(name) => { self.device = name; }, SendState => { self.statetx.send(linesparsed).unwrap(); } tickinterval: 5, // Every 5ms, default = 100 ontick: // onmessage have priority over ontick linesparsed += 1; onstop: () // customcode must end with a semicolon customcode: pub const DEFAULTDEVICE: &'static str = "video0"; }

[test]

fn teststreamparsingactor() { use StreamParsingActor::{Actor, Input, DEFAULTDEVICE};

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();

} ```

Actor attributes

These words if followed by colon, are restricted keywords.

Some code can break macro internals (e.g. break or continue without defining your own loop can break actor's main loop, putting on_stop: (), will result in an invalid comma). Debugging it can be cryptic, hopefully [actor_dbg] (when the code doesn't compile) and [cargo-expand] (when it does) will help you in such situations.

History

Previously, I've written [x11-input-supercharger], an utility for auto-scrolling and conditional mouse-to-keyboard rebinding (without changing the keymap itself, which causes Chromium-based applications to freeze for a second). It had many threads running - one for auto-scrolling, other for rebinding, another for polling X11 events, and yet another for displaying GTK3 window with Windows-like indicator of auto-scrolling state. The code was not that complex, and the messaging hierarchy was simple (parent messaged its children, never the other way around), yet the boilerplate grown to become quite a hindrance when it comes to code readability, especially since the threads also had to do their own work in addition to reacting to messages (the work includes interacting with X11, GTK3, and tracking time). Instead of using [actix], I decided to try to implement my own library, inspired by [actress], and spent about 12 hours doing so (I'm far from being a fluent Rust programmer, also, this was my first time using procedural macros).

This is my first "real" library (my previous crates/Rust programs were CLI/GUI utilities, sometimes with simple public Rust API). I published some of them to crates.io, but not all (some are on [Github], some not published yet). I learned about procedural macros (mostly about their current shortcomings) - sadly, there are not many good resources about them. I sticked mostly to Rust reference. Surprisingly, I was also unable to quickly Google the "right" way to do documentation (with links and examples) - so I used the first edition of Rust book. I should probably finally read the second edition to familiarize myself with what's inside and where it is (back when I started, there was no second edition yet).

Usage in the wild

If you use this library in your project, consider putting it here (if possible). It will help me with testing whether the new change I introduce breaks anything.

Docs

README.md is built using [cargo-readme] (cargo readme > README.md). The documentation is built by [docs.sh] script.

License

Copyright 2019 Paweł Zmarzły. Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

License: MIT OR Apache-2.0