#![doc(html_root_url = "https://movie.pzmarzly.pl")]
extern crate proc_macro;
use proc_macro::TokenStream;
use std::collections::HashMap;
#[proc_macro]
pub fn actor(input: TokenStream) -> TokenStream {
actor_internal(input, false)
}
#[proc_macro]
pub fn actor_dbg(input: TokenStream) -> TokenStream {
actor_internal(input, true)
}
fn actor_internal(input: TokenStream, debug: bool) -> TokenStream {
let input = input.to_string();
if debug {
eprintln!("Input:");
eprintln!("{}", input);
}
let supported_attributes = vec![
("public_visibility", ""),
("docs", ""),
("input", ""),
("input_derive", ""),
("data", ""),
("on_init", ""),
("on_message", ""),
("tick_interval", "100"),
("on_tick", ""),
("on_stop", ""),
("spawner", "std::thread::spawn"),
("spawner_return_type", "std::thread::JoinHandle<()>"),
("custom_code", ""),
];
let mut locations = vec![(0, "name", 0)];
for attr in &supported_attributes {
let attr = attr.0;
let search_strings = &[
format!("\n{} :\n", attr),
format!(" {} :\n", attr),
format!("\n{} : ", attr),
format!(" {} : ", attr),
format!("\n{}\n:\n", attr),
format!(" {}\n:\n", attr),
format!("\n{}\n: ", attr),
format!(" {}\n: ", attr),
];
for search_str in search_strings {
let pos = input.find(search_str);
if let Some(pos) = pos {
locations.push((pos, attr, pos + search_str.len()));
break;
}
}
}
let mut attrs: HashMap<&str, String> = HashMap::new();
locations.sort_unstable();
for i in 0..locations.len() {
let value = if i == locations.len() - 1 {
&input[locations[i].2..]
} else {
&input[locations[i].2..locations[i + 1].0]
};
attrs.insert(locations[i].1, value.to_string());
}
if debug {
eprintln!("Parsed attributes:");
eprintln!("{:?}", &attrs);
}
for attr in &supported_attributes {
attrs.entry(attr.0).or_insert(attr.1.to_string());
}
let public_visibility = if attrs["public_visibility"].contains("true") {
"pub".to_string()
} else {
"".to_string()
};
let input_derive = if attrs["input_derive"].len() > 0 {
format!("#[derive({})]", attrs["input_derive"])
} else {
"".to_string()
};
let output = format!(
"
{docs}
{public_visibility} mod {name} {{
use super::*;
{custom_code}
pub struct Actor {{
{data}
}}
{input_derive}
pub enum Input {{
{input}
}}
pub type Handle = movie::Handle<{spawner_return_type}, Input>;
impl Actor {{
pub fn start(mut self) -> Handle
{{
let (tx_ota, rx_ota) = std::sync::mpsc::channel(); // owner-to-actor data
let (tx_kill, rx_kill) = std::sync::mpsc::channel(); // owner-to-actor stop requests
let handle = {spawner}(move || {{
{on_init} // on_init is not separated as this is the simplest way to
// implement thread-local data. This may change in later (breaking)
// updates
let mut running = true;
while running {{
while let Ok(message) = rx_ota.try_recv() {{
use Input::*;
match message {{
{on_message}
}};
}}
if let Ok(_) = rx_kill.try_recv() {{
running = false;
{{
{on_stop}
}};
}}
{{
{on_tick}
}};
use std::thread::sleep;
use std::time::Duration;
sleep(Duration::from_millis({tick_interval}));
}}
}});
movie::Handle {{
join_handle: handle,
tx: tx_ota,
kill: tx_kill,
}}
}}
}}
}}",
name = attrs["name"],
docs = attrs["docs"],
input = attrs["input"],
data = attrs["data"],
on_init = attrs["on_init"],
on_message = attrs["on_message"],
tick_interval = attrs["tick_interval"],
on_tick = attrs["on_tick"],
on_stop = attrs["on_stop"],
spawner = attrs["spawner"],
spawner_return_type = attrs["spawner_return_type"],
custom_code = attrs["custom_code"],
public_visibility = public_visibility,
input_derive = input_derive,
);
if debug {
eprintln!("Generated code:");
eprintln!("{}", output);
}
output.parse().unwrap()
}