diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 67b4b39..dc467fa 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" +dependencies = [ + "memchr", +] + [[package]] name = "async-recursion" version = "1.0.0" @@ -416,6 +425,7 @@ dependencies = [ "async-trait", "clap", "log", + "regex", "serde", "serde_json", "simplelog", @@ -664,6 +674,23 @@ dependencies = [ "bitflags", ] +[[package]] +name = "regex" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" + [[package]] name = "remove_dir_all" version = "0.5.3" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 3f3a1b5..1acd56c 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -20,6 +20,8 @@ async-trait = "0.1.57" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +regex = { version = "1" } + # logging log = "0.4" simplelog = "0.12" diff --git a/backend/src/api/async_utils.rs b/backend/src/api/async_utils.rs index 3dc530c..9d03c2b 100644 --- a/backend/src/api/async_utils.rs +++ b/backend/src/api/async_utils.rs @@ -1,19 +1,9 @@ -use std::sync::mpsc::{TryRecvError, Receiver}; -use std::sync::Mutex; +use std::sync::mpsc::{RecvError, Receiver}; -/// Receive on a blocking channel in an async manner (by polling conservatively) -pub async fn channel_recv(rx: Receiver) -> Result { - let sleep_duration = std::time::Duration::from_millis(10); - let receiver = Mutex::new(rx); - loop { - let received = receiver.lock().unwrap().try_recv(); - match received { - Err(TryRecvError::Disconnected) => { - return Err(TryRecvError::Disconnected); - }, - Err(_) => {}, - Ok(x) => return Ok(x), - } - tokio::time::sleep(sleep_duration).await; - } +/// Receive on a blocking channel in an async manner +pub async fn channel_recv(rx: Receiver) -> Result { + tokio::task::spawn_blocking(move || rx.recv()).await.map_err(|e| { + log::error!("Async JoinError while receiving from sync channel: {}", e); + RecvError + })? } diff --git a/backend/src/api/get_display.rs b/backend/src/api/get_display.rs index 6e8a850..3c0302a 100644 --- a/backend/src/api/get_display.rs +++ b/backend/src/api/get_display.rs @@ -1,4 +1,4 @@ -use std::sync::{Mutex, mpsc::{Sender, channel, self}}; +use std::sync::{Mutex, mpsc::{Sender, channel}}; use usdpl_back::core::serdes::Primitive; use usdpl_back::AsyncCallable; @@ -44,15 +44,14 @@ impl AsyncCallable for GetDisplayEndpoint { ); match send_result { Ok(_) => { - // TODO: don't poll for response log::info!("waiting for display for item #{}", index); match super::async_utils::channel_recv(receiver).await { - Err(mpsc::TryRecvError::Disconnected) => { + Err(_) => { let msg = format!("Failed to response for get_display for #{}", index); log::warn!("{}", msg); return vec![ApiDisplayResult::failure(msg, "receiving channel disconnected").to_primitive()]; }, - Err(_) => return vec![], // impossible + //Err(_) => return vec![], // impossible Ok(x) => { log::debug!("got display for item #{}", index); return vec![ApiDisplayResult::success(x).to_primitive()]; diff --git a/backend/src/api/types.rs b/backend/src/api/types.rs index 0167abe..15f8bd6 100644 --- a/backend/src/api/types.rs +++ b/backend/src/api/types.rs @@ -3,7 +3,7 @@ use serde_json::Value; use usdpl_back::core::serdes::Primitive; #[derive(Serialize, Deserialize, Clone)] -#[serde(tag = "element")] +#[serde(tag = "result")] pub enum ApiDisplayResult { #[serde(rename = "value")] Value(ApiValue), diff --git a/backend/src/config/action.rs b/backend/src/config/action.rs index b379355..af65488 100644 --- a/backend/src/config/action.rs +++ b/backend/src/config/action.rs @@ -3,11 +3,18 @@ use serde::{Serialize, Deserialize}; #[derive(Serialize, Deserialize, Clone)] #[serde(tag = "action")] pub enum ActionConfig { - #[serde(rename = "command")] + #[serde(rename = "command")] Command(CommandAction), + #[serde(rename = "transform")] + Transform(super::TransformAction), + #[serde(rename = "mirror")] + Mirror(MirrorAction), } #[derive(Serialize, Deserialize, Clone)] pub struct CommandAction { pub run: String, } + +#[derive(Serialize, Deserialize, Clone)] +pub struct MirrorAction; diff --git a/backend/src/config/base.rs b/backend/src/config/base.rs index 157df98..d576eae 100644 --- a/backend/src/config/base.rs +++ b/backend/src/config/base.rs @@ -21,12 +21,18 @@ impl BaseConfig { let reader = std::io::BufReader::new(file); match serde_json::from_reader(reader) { Ok(conf) => return conf, - Err(e) => log::error!("Failed to deserialize {}: {}", path.display(), e), + Err(e) => { + log::error!("Failed to deserialize {}: {}", path.display(), e); + panic!("Failed to deserialize {}: {}", path.display(), e) + }, } }, - Err(e) => log::error!("Failed to open {}: {}", path.display(), e), + Err(e) => { + log::error!("Failed to open {}: {}", path.display(), e); + panic!("Failed to open {}: {}", path.display(), e); + } } - panic!("Cannot open {}", path.display()) + //panic!("Cannot open {}", path.display()) } #[inline] diff --git a/backend/src/config/mod.rs b/backend/src/config/mod.rs index d922af5..0ed3373 100644 --- a/backend/src/config/mod.rs +++ b/backend/src/config/mod.rs @@ -7,9 +7,10 @@ mod reading; mod result_display; mod slider; mod toggle; +mod transformer; pub use about::AboutConfig; -pub use action::{ActionConfig, CommandAction}; +pub use action::{ActionConfig, CommandAction, MirrorAction}; pub use base::BaseConfig; pub use button::ButtonConfig; pub use element::ElementConfig; @@ -17,6 +18,7 @@ pub use reading::ReadingConfig; pub use result_display::ResultDisplayConfig; pub use slider::SliderConfig; pub use toggle::ToggleConfig; +pub use transformer::{TransformAction, TransformTypeAction, ReplaceTransformAction, ExpandTransformAction, LogTransformAction, LogLevel, PatternConfig}; #[cfg(test)] mod test { diff --git a/backend/src/config/transformer.rs b/backend/src/config/transformer.rs new file mode 100644 index 0000000..e84f64c --- /dev/null +++ b/backend/src/config/transformer.rs @@ -0,0 +1,69 @@ +//! Transformers, robots in disguise! (or value-based transformations) + +use serde::{Serialize, Deserialize}; + +use super::ActionConfig; + +#[derive(Serialize, Deserialize, Clone)] +pub struct TransformAction { + pub target: Box, + pub transformer: TransformTypeAction, +} + +#[derive(Serialize, Deserialize, Clone)] +#[serde(tag = "rule")] +pub enum TransformTypeAction { + #[serde(rename = "pre-replace")] + PreReplace(ReplaceTransformAction), + #[serde(rename = "post-replace")] + PostReplace(ReplaceTransformAction), + #[serde(rename = "pre-expand")] + PreExpand(ExpandTransformAction), + #[serde(rename = "post-expand")] + PostExpand(ExpandTransformAction), + #[serde(rename = "log")] + Log(LogTransformAction), +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct ReplaceTransformAction { + /// Regex + pub patterns: Vec, +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct PatternConfig { + /// Regex + pub pattern: String, + /// Formatting info https://docs.rs/regex/latest/regex/struct.Regex.html#replacement-string-syntax + pub format: String, + // Regex case_insensitive flags + pub i: Option, + // Regex multi_line flags + pub m: Option, + // Regex dot_matches_new_line flags + pub s: Option, + // Regex swap_greed flags + #[serde(rename = "U")] + pub u: Option, + // Regex ignore_whitespace flags + pub x: Option, +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct ExpandTransformAction { + pub format: String, +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct LogTransformAction { + pub level: LogLevel, +} + +#[derive(Serialize, Deserialize, Clone)] +pub enum LogLevel { + DEBUG, + INFO, + WARN, + ERROR, +} diff --git a/backend/src/runtime/actor.rs b/backend/src/runtime/actors/actor.rs similarity index 70% rename from backend/src/runtime/actor.rs rename to backend/src/runtime/actors/actor.rs index 137caac..2faba38 100644 --- a/backend/src/runtime/actor.rs +++ b/backend/src/runtime/actors/actor.rs @@ -5,11 +5,11 @@ use crate::config::{ElementConfig, ActionConfig}; pub type ActError = String; /// Something capable of performing an action. -pub trait Act: Sized { +pub trait Act<'a>: Sized + 'a { type Param; - type Config: ?Sized; + type Config: ?Sized + 'a; type Return; - fn build(config: &Self::Config, parameter: Self::Param) -> Result; + fn build(config: &'a Self::Config, parameter: Self::Param) -> Result; fn run(self) -> Self::Return; } @@ -19,12 +19,12 @@ pub struct Actor { index: usize, } -impl Act for Actor { +impl<'a> Act<'a> for Actor { type Param = (usize, Primitive); type Config = ElementConfig; type Return = Primitive; - fn build(config: &ElementConfig, parameter: Self::Param) -> Result { + fn build(config: &'a ElementConfig, parameter: Self::Param) -> Result { let a_type = match config { ElementConfig::Button(b) => ActorType::build(&b.on_click, parameter.1), ElementConfig::Toggle(t) => ActorType::build(&t.on_toggle, parameter.1), @@ -48,23 +48,31 @@ impl Act for Actor { pub enum ActorType { Command(super::CommandActor), + Transform(super::TransformActor), + Mirror(Primitive), } -impl Act for ActorType { +impl<'a> Act<'a> for ActorType { type Param = Primitive; type Config = ActionConfig; type Return = Primitive; - fn build(config: &Self::Config, parameter: Self::Param) -> Result { + fn build(config: &'a Self::Config, parameter: Self::Param) -> Result { Ok(match config { ActionConfig::Command(c) => Self::Command(super::CommandActor::build(c, parameter)?), + ActionConfig::Transform(t) => + Self::Transform(super::TransformActor::build(t, parameter)?), + ActionConfig::Mirror(_) => + Self::Mirror(parameter) }) } fn run(self) -> Self::Return { match self { Self::Command(c) => c.run().into(), + Self::Transform(t) => t.run(), + Self::Mirror(p) => p, } } } diff --git a/backend/src/runtime/command_actor.rs b/backend/src/runtime/actors/command_actor.rs similarity index 86% rename from backend/src/runtime/command_actor.rs rename to backend/src/runtime/actors/command_actor.rs index e0f692b..886d4bf 100644 --- a/backend/src/runtime/command_actor.rs +++ b/backend/src/runtime/actors/command_actor.rs @@ -5,8 +5,6 @@ use usdpl_back::core::serdes::Primitive; use crate::config::CommandAction; use super::{Act, ActError}; -const VALUE_ENV_VAR: &str = "KAYLON_VALUE"; - /// Runs a CLI command in Bash pub struct CommandActor { shell: String, @@ -31,12 +29,12 @@ impl CommandActor { } } -impl Act for CommandActor { +impl<'a> Act<'a> for CommandActor { type Param = Primitive; type Config = CommandAction; type Return = String; - fn build(config: &CommandAction, parameter: Primitive) -> Result { + fn build(config: &'a CommandAction, parameter: Primitive) -> Result { Ok( Self { shell: "bash".to_owned(), @@ -49,14 +47,14 @@ impl Act for CommandActor { fn run(self) -> Self::Return { let output = Command::new(&self.shell) .args(["-c", &self.run]) - .env(VALUE_ENV_VAR, &self.variable) + .env(super::VALUE_VAR, &self.variable) .output() .expect(&format!("Cannot run `{}`", &self.run)); if !output.stderr.is_empty() { log::error!("Error running `{}`: {}", &self.run, String::from_utf8(output.stderr).unwrap_or_else(|_| "".to_owned())) } let result = String::from_utf8(output.stdout).expect(&format!("Cannot parse stdout from `{}` as UTF-8", self.run)); - log::debug!("CommandActor ran `{}` (${}=\"{}\") -> `{}`", &self.run, VALUE_ENV_VAR, &self.variable, &result); + log::debug!("CommandActor ran `{}` (${}=\"{}\") -> `{}`", &self.run, super::VALUE_VAR, &self.variable, &result); result } } diff --git a/backend/src/runtime/actors/mod.rs b/backend/src/runtime/actors/mod.rs new file mode 100644 index 0000000..a0bbb93 --- /dev/null +++ b/backend/src/runtime/actors/mod.rs @@ -0,0 +1,11 @@ +mod actor; +mod command_actor; +mod periodic_actor; +mod transform_actor; + +pub use actor::{Actor, Act, ActError, ActorType}; +pub use command_actor::CommandActor; +pub use periodic_actor::PeriodicActor; +pub use transform_actor::TransformActor; + +pub const VALUE_VAR: &str = "KAYLON_VALUE"; diff --git a/backend/src/runtime/periodic_actor.rs b/backend/src/runtime/actors/periodic_actor.rs similarity index 89% rename from backend/src/runtime/periodic_actor.rs rename to backend/src/runtime/actors/periodic_actor.rs index db65de5..ec7fe56 100644 --- a/backend/src/runtime/periodic_actor.rs +++ b/backend/src/runtime/actors/periodic_actor.rs @@ -4,7 +4,8 @@ use std::time::Duration; use usdpl_back::core::serdes::Primitive; use crate::config::ReadingConfig; -use super::{Act, ActError, ActorType, RouterCommand}; +use super::{Act, ActError, ActorType}; +use crate::runtime::RouterCommand; /// Runs an action periodically pub struct PeriodicActor { @@ -13,12 +14,12 @@ pub struct PeriodicActor { index: usize, } -impl Act for PeriodicActor { +impl<'a> Act<'a> for PeriodicActor { type Param = (usize, Sender); type Config = ReadingConfig; type Return = (); - fn build(config: &Self::Config, parameter: Self::Param) -> Result { + fn build(config: &'a Self::Config, parameter: Self::Param) -> Result { ActorType::build(&config.on_period, Primitive::Empty)?; Ok( Self { diff --git a/backend/src/runtime/actors/transform_actor.rs b/backend/src/runtime/actors/transform_actor.rs new file mode 100644 index 0000000..10ab3e6 --- /dev/null +++ b/backend/src/runtime/actors/transform_actor.rs @@ -0,0 +1,320 @@ +use regex::{Regex, RegexBuilder}; +use usdpl_back::core::serdes::Primitive; + +use crate::runtime::primitive_utils; + +use crate::config::{TransformAction, ActionConfig, TransformTypeAction, ReplaceTransformAction, ExpandTransformAction, LogTransformAction, LogLevel, PatternConfig}; +use super::{Act, ActError, ActorType}; + +/// Changes the output or input of an act +pub enum TransformActor { + PreReplace(PreReplaceTransformActor), + PostReplace(PostReplaceTransformActor), + PreExpand(PreExpandTransformActor), + PostExpand(PostExpandTransformActor), + Log(LogTransformActor), +} + +impl<'a> Act<'a> for TransformActor { + type Param = Primitive; + type Config = TransformAction; + type Return = Primitive; + + fn build(config: &'a TransformAction, parameter: Primitive) -> Result { + let result = Ok(match &config.transformer { + TransformTypeAction::PreReplace(x) => + Self::PreReplace(PreReplaceTransformActor::build(&(x, &config.target), parameter)?), + TransformTypeAction::PostReplace(x) => + Self::PostReplace(PostReplaceTransformActor::build(&(x, &config.target), parameter)?), + TransformTypeAction::PreExpand(x) => + Self::PreExpand(PreExpandTransformActor::build(&(x, &config.target), parameter)?), + TransformTypeAction::PostExpand(x) => + Self::PostExpand(PostExpandTransformActor::build(&(x, &config.target), parameter)?), + TransformTypeAction::Log(x) => + Self::Log(LogTransformActor::build(&(x, &config.target), parameter)?), + }); + result + } + + fn run(self) -> Self::Return { + match self { + Self::PreReplace(x) => x.run(), + Self::PostReplace(x) => x.run(), + Self::PreExpand(x) => x.run(), + Self::PostExpand(x) => x.run(), + Self::Log(x) => x.run() + } + } +} + +pub(super) struct TransformPostActor { + op_fn: Box Primitive) + Send>, + actor: Box, +} + +impl<'a> Act<'a> for TransformPostActor { + type Param = (Primitive, Box Primitive) + Send>); + type Config = ActionConfig; + type Return = Primitive; + + fn build(config: &'a Self::Config, parameter: Self::Param) -> Result { + Ok( + Self { + op_fn: parameter.1, + actor: Box::new(ActorType::build(config, parameter.0)?), + } + ) + } + + fn run(self) -> Self::Return { + (self.op_fn)(self.actor.run()) + } +} + +/// executes op_fn and ActionConfig::build in Actor::build() +/// this blocks the main execution thread, +/// but an Err() from ActionConfig::build will be be propogated correctly +pub(super) struct TransformEagerPreActor { + actor: Box, +} + +impl<'a> Act<'a> for TransformEagerPreActor { + type Param = (Primitive, Box Primitive) + Send>); + type Config = ActionConfig; + type Return = Primitive; + + fn build(config: &'a Self::Config, parameter: Self::Param) -> Result { + let primitive = (parameter.1)(parameter.0); + Ok( + Self { + actor: Box::new(ActorType::build(config, primitive)?), + } + ) + } + + fn run(self) -> Self::Return { + self.actor.run() + } +} + +/// executes op_fn and ActionConfig::build in Actor.run() +/// this doesn't block the main execution thread, +/// but an Err() from ActionConfig::build will produce an empty result +pub(super) struct TransformLazyPreActor { + op_fn: Box Primitive) + Send>, + action: ActionConfig, + primitive: Primitive, +} + +impl<'a> Act<'a> for TransformLazyPreActor { + type Param = (Primitive, Box Primitive) + Send>); + type Config = ActionConfig; + type Return = Primitive; + + fn build(config: &'a Self::Config, parameter: Self::Param) -> Result { + Ok( + Self { + op_fn: parameter.1, + action: config.to_owned(), + primitive: parameter.0, + } + ) + } + + fn run(self) -> Self::Return { + let primitive = (self.op_fn)(self.primitive); + match ActorType::build(&self.action, primitive) { + Ok(action) => action.run(), + Err(e) => { + log::error!("Failed to lazily build action for pre-transformer: {}", e); + Primitive::Empty + } + } + } +} + +struct PatternRule { + pattern: Regex, + format: String, +} + +impl PatternRule { + #[inline] + fn from_config(config: &PatternConfig) -> Result { + let re = RegexBuilder::new(&config.pattern) + .case_insensitive(config.i.unwrap_or(false)) + .multi_line(config.m.unwrap_or(false)) + .dot_matches_new_line(config.s.unwrap_or(false)) + .swap_greed(config.u.unwrap_or(false)) + .ignore_whitespace(config.x.unwrap_or(false)) + .build() + .map_err(|e| format!("Failed to compile regex `{}`: {}", config.pattern, e))?; + Ok(Self { + pattern: re, + format: config.format.clone(), + }) + } +} + +fn replace_fn(config: &ReplaceTransformAction) -> Result Primitive, ActError> { + let mut patterns: Vec = Vec::with_capacity(config.patterns.len()); + for pattern in config.patterns.iter() { + patterns.push(PatternRule::from_config(pattern)?); + } + + Ok(move |p| { + let mut stringy = primitive_utils::display(p); + for pattern in patterns { + stringy = pattern.pattern.replace(&stringy, pattern.format).into_owned(); + } + stringy.into() + }) +} + +pub struct PreReplaceTransformActor { + transformer: TransformEagerPreActor, +} + +impl<'a> Act<'a> for PreReplaceTransformActor { + type Param = Primitive; + type Config = (&'a ReplaceTransformAction, &'a ActionConfig); + type Return = Primitive; + + fn build(config: &'a Self::Config, parameter: Primitive) -> Result { + Ok( + Self { + transformer: TransformEagerPreActor::build( + config.1, + (parameter, + Box::new(replace_fn(config.0)?) + ))?, + } + ) + } + + fn run(self) -> Self::Return { + self.transformer.run() + } +} + +pub struct PostReplaceTransformActor { + transformer: TransformPostActor, +} + +impl<'a> Act<'a> for PostReplaceTransformActor { + type Param = Primitive; + type Config = (&'a ReplaceTransformAction, &'a ActionConfig); + type Return = Primitive; + + fn build(config: &'a Self::Config, parameter: Primitive) -> Result { + + Ok( + Self { + transformer: TransformPostActor::build( + config.1, + (parameter, + Box::new(replace_fn(config.0)?) + ))?, + } + ) + } + + fn run(self) -> Self::Return { + self.transformer.run() + } +} + +fn expand_fn(config: &ExpandTransformAction) -> Result Primitive, ActError> { + let format = config.format.clone(); + Ok(move |p| { + let stringy = primitive_utils::display(p); + let pattern1 = format!("${}", super::VALUE_VAR); + let pattern2 = format!("${{{}}}", super::VALUE_VAR); + format.replace(&pattern1, &pattern2).replace(&pattern2, &stringy).into() + }) +} + +pub struct PreExpandTransformActor { + transformer: TransformLazyPreActor, +} + + +impl<'a> Act<'a> for PreExpandTransformActor { + type Param = Primitive; + type Config = (&'a ExpandTransformAction, &'a ActionConfig); + type Return = Primitive; + + fn build(config: &'a Self::Config, parameter: Primitive) -> Result { + Ok( + Self { + transformer: TransformLazyPreActor::build( + config.1, + (parameter, + Box::new(expand_fn(config.0)?) + ))?, + } + ) + } + + fn run(self) -> Self::Return { + self.transformer.run() + } +} + +pub struct PostExpandTransformActor { + transformer: TransformPostActor, +} + + +impl<'a> Act<'a> for PostExpandTransformActor { + type Param = Primitive; + type Config = (&'a ExpandTransformAction, &'a ActionConfig); + type Return = Primitive; + + fn build(config: &'a Self::Config, parameter: Primitive) -> Result { + Ok( + Self { + transformer: TransformPostActor::build( + config.1, + (parameter, + Box::new(expand_fn(config.0)?) + ))?, + } + ) + } + + fn run(self) -> Self::Return { + self.transformer.run() + } +} + +pub struct LogTransformActor { + generic: TransformPostActor, +} + +impl<'a> Act<'a> for LogTransformActor { + type Param = Primitive; + type Config = (&'a LogTransformAction, &'a ActionConfig); + type Return = Primitive; + + fn build(config: &'a Self::Config, parameter: Primitive) -> Result { + Ok( + Self { + generic: TransformPostActor::build( + config.1, + (parameter, + match config.0.level { + LogLevel::DEBUG => Box::new(|p| {log::debug!("{}", primitive_utils::debug(&p));p}), + LogLevel::INFO => Box::new(|p| {log::info!("{}", primitive_utils::debug(&p));p}), + LogLevel::WARN => Box::new(|p| {log::warn!("{}", primitive_utils::debug(&p));p}), + LogLevel::ERROR => Box::new(|p| {log::error!("{}", primitive_utils::debug(&p));p}), + } + ))?, + } + ) + } + + fn run(self) -> Self::Return { + self.generic.run() + } +} diff --git a/backend/src/runtime/mod.rs b/backend/src/runtime/mod.rs index 53e89cb..8f73a7a 100644 --- a/backend/src/runtime/mod.rs +++ b/backend/src/runtime/mod.rs @@ -1,14 +1,10 @@ -mod actor; -mod command_actor; +mod actors; mod communication; mod executor; -mod periodic_actor; mod primitive_utils; mod result_router; -pub use actor::{Actor, Act, ActError, ActorType}; -pub use command_actor::CommandActor; +pub use actors::*; pub use communication::{QueueItem, QueueAction}; pub use executor::RuntimeExecutor; -pub use periodic_actor::PeriodicActor; pub use result_router::{ResultRouter, RouterCommand}; diff --git a/backend/src/runtime/primitive_utils.rs b/backend/src/runtime/primitive_utils.rs index 715d2a5..55572ef 100644 --- a/backend/src/runtime/primitive_utils.rs +++ b/backend/src/runtime/primitive_utils.rs @@ -51,7 +51,7 @@ pub fn debug(primitive: &Primitive) -> String { } } -/*#[inline] +#[inline] pub fn display(primitive: Primitive) -> String { match primitive { Primitive::Empty => "".to_owned(), @@ -65,7 +65,7 @@ pub fn display(primitive: Primitive) -> String { Primitive::Bool(x) => x.to_string(), Primitive::Json(x) => x, } -}*/ +} #[inline] pub fn clone(primitive: &Primitive) -> Primitive { diff --git a/backend/src/runtime/result_router.rs b/backend/src/runtime/result_router.rs index 03c42e4..cd21d20 100644 --- a/backend/src/runtime/result_router.rs +++ b/backend/src/runtime/result_router.rs @@ -40,12 +40,12 @@ impl ResultRouter { } } -impl Act for ResultRouter { +impl<'a> Act<'a> for ResultRouter { type Param = usize; type Config = (); type Return = Sender; - fn build(_config: &Self::Config, parameter: Self::Param) -> Result { + fn build(_config: &'a Self::Config, parameter: Self::Param) -> Result { let (tx, rx) = mpsc::channel(); let mut cache_vec = Vec::with_capacity(parameter); for _ in 0..parameter { diff --git a/kaylon.json b/kaylon.json index 51c782d..862e5d9 100644 --- a/kaylon.json +++ b/kaylon.json @@ -30,7 +30,7 @@ }, { "element": "result-display", - "title": "", + "title": " ", "result_of": 3 }, { @@ -46,7 +46,7 @@ }, { "element": "result-display", - "title": "", + "title": " ", "result_of": 5 }, { @@ -54,8 +54,41 @@ "title": "Fan Speed", "period_ms": 1000, "on_period": { - "action": "command", - "run": "cat /sys/class/hwmon/hwmon5/fan1_input" + "action": "transform", + "target": { + "action": "command", + "run": "cat /sys/class/hwmon/hwmon5/fan1_input" + }, + "transformer": { + "rule": "post-expand", + "format": "$KAYLON_VALUE RPM" + } + } + }, + { + "element": "toggle", + "title": "Lighthouses", + "description": "Turn Valve Index Lighthouses on", + "on_toggle": { + "action": "transform", + "target": { + "action": "transform", + "target": { + "action": "command", + "run": "python3 ./bin/lighthouse_ctrl.py ${KAYLON_VALUE}" + }, + "transformer": { + "rule": "post-replace", + "patterns": [{"pattern": ".*", "format": "Done", "s": true}] + } + }, + "transformer": { + "rule": "pre-replace", + "patterns": [ + {"pattern": "TRUE", "format": "on", "i": true}, + {"pattern": "FALSE", "format": "off", "i": true} + ] + } } } ], diff --git a/src/backend.ts b/src/backend.ts index ef00e93..3045362 100644 --- a/src/backend.ts +++ b/src/backend.ts @@ -67,13 +67,13 @@ export type CResultDisplay = { export type CElement = CButton | CToggle | CSlider | CReading | CResultDisplay; export type CErrorResult = { - element: string; // "error" + result: string; // "error" message: string; exception: string; } export type CValueResult = { - element: string; // "value" + result: string; // "value" value: any; } diff --git a/src/index.tsx b/src/index.tsx index 5962ecf..2d03ae9 100755 --- a/src/index.tsx +++ b/src/index.tsx @@ -32,10 +32,12 @@ let about: backend.CAbout | null = null; let update = () => {}; +let updateTasks: (() => void)[] = []; + function displayCallback(index: number) { return (newVal: backend.CDisplayResponse) => { if (newVal != null) { - switch (newVal.element) { + switch (newVal.result) { case "value": let val = newVal as backend.CValueResult; console.log("KAYLON: Got display for " + index.toString(), val); @@ -52,11 +54,18 @@ function displayCallback(index: number) { } else { console.warn("KAYLON: Ignoring null display result for " + index.toString()); } - backend.resolve(backend.getDisplay(index), displayCallback(index)); + updateTasks.push(() => backend.resolve(backend.getDisplay(index), displayCallback(index))); update(); } } +function onGetElements() { + for (let i = 0; i < items.length; i++) { + console.log("KAYLON: req display for item #" + i.toString()); + backend.resolve(backend.getDisplay(i), displayCallback(i)); + } +} + // init USDPL WASM frontend // this is required to interface with the backend (async () => { @@ -68,11 +77,8 @@ function displayCallback(index: number) { let result = await elements_promise; console.log("KAYLON: got elements", result); if (result != null) { - items = await backend.getElements(); - for (let i = 0; i < items.length; i++) { - console.log("KAYLON: req display for item #" + i.toString()); - backend.resolve(backend.getDisplay(i), displayCallback(i)); - } + items = result; + onGetElements(); } else { console.warn("KAYLON: backend connection failed"); } @@ -90,6 +96,13 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => { update(); } + // perform tasks (like updating display elements) only while rendering the plugin + let taskItem = updateTasks.pop(); + while (taskItem != undefined) { + taskItem(); + taskItem = updateTasks.pop(); + } + return ( {items.map( @@ -106,6 +119,12 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => { (reload_items: backend.CElement[]) => { items = reload_items; console.log("KAYLON: got elements", reload_items); + if (reload_items != null) { + items = reload_items; + onGetElements(); + } else { + console.warn("KAYLON: backend connection failed"); + } update(); }); backend.resolve(backend.getAbout(),