cog-task 1.2.0

A general-purpose low-latency application to run cognitive tasks
Documentation
use crate::action::{Action, ActionSignal, Props, StatefulAction, INFINITE};
use crate::comm::{QWriter, Signal, SignalId};
use crate::resource::{IoManager, LoggerSignal, OptionalString, ResourceManager};
use crate::server::{AsyncSignal, Config, State, SyncSignal};
use chrono::Local;
use eyre::{eyre, Result};
use serde::{Deserialize, Serialize};
use serde_cbor::Value;
use std::collections::BTreeSet;
use std::time::Instant;

#[derive(Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct KeyLogger {
    #[serde(default = "defaults::group")]
    group: OptionalString,
    #[serde(default)]
    out_key: SignalId,
}

stateful!(KeyLogger {
    group: Option<String>,
    out_key: SignalId,
});

mod defaults {
    use crate::resource::OptionalString;

    #[inline(always)]
    pub fn group() -> OptionalString {
        OptionalString::Some("keypress".to_owned())
    }
}

impl Action for KeyLogger {
    fn out_signals(&self) -> BTreeSet<SignalId> {
        BTreeSet::from([self.out_key])
    }

    fn stateful(
        &self,
        _io: &IoManager,
        _res: &ResourceManager,
        _config: &Config,
        _sync_writer: &QWriter<SyncSignal>,
        _async_writer: &QWriter<AsyncSignal>,
    ) -> Result<Box<dyn StatefulAction>> {
        if self.group.is_none() && self.out_key == 0 {
            return Err(eyre!(
                "Both `group` and `out_key` for KeyLogger cannot be empty simultaneously."
            ));
        }

        let group = match &self.group {
            OptionalString::Some(s) => Some(s.clone()),
            OptionalString::None => None,
        };

        Ok(Box::new(StatefulKeyLogger {
            done: false,
            group,
            out_key: self.out_key,
        }))
    }
}

impl StatefulAction for StatefulKeyLogger {
    impl_stateful!();

    #[inline(always)]
    fn props(&self) -> Props {
        INFINITE.into()
    }

    fn start(
        &mut self,
        _sync_writer: &mut QWriter<SyncSignal>,
        async_writer: &mut QWriter<AsyncSignal>,
        _state: &State,
    ) -> Result<Signal> {
        if let Some(group) = self.group.as_ref() {
            async_writer.push(LoggerSignal::Append(
                group.clone(),
                ("event".to_owned(), Value::Text("start".to_owned())),
            ));
        }

        Ok(Signal::none())
    }

    fn update(
        &mut self,
        signal: &ActionSignal,
        sync_writer: &mut QWriter<SyncSignal>,
        async_writer: &mut QWriter<AsyncSignal>,
        _state: &State,
    ) -> Result<Signal> {
        if let ActionSignal::KeyPress(_, keys) = signal {
            let entry = (
                "key".to_string(),
                Value::Array(keys.iter().map(|k| Value::Text(format!("{k:?}"))).collect()),
            );

            if self.out_key > 0 {
                sync_writer.push(SyncSignal::Emit(
                    Instant::now(),
                    Signal::from(
                        keys.iter()
                            .map(|k| (self.out_key, Value::Text(format!("{k:?}"))))
                            .collect::<Vec<_>>(),
                    ),
                ));
            }

            if let Some(group) = self.group.as_ref() {
                async_writer.push(AsyncSignal::Logger(
                    Local::now(),
                    LoggerSignal::Append(group.clone(), entry),
                ));
            }
        }

        Ok(Signal::none())
    }

    #[inline]
    fn stop(
        &mut self,
        _sync_writer: &mut QWriter<SyncSignal>,
        async_writer: &mut QWriter<AsyncSignal>,
        _state: &State,
    ) -> Result<Signal> {
        if let Some(group) = self.group.as_ref() {
            async_writer.push(LoggerSignal::Append(
                group.clone(),
                ("event".to_owned(), Value::Text("stop".to_owned())),
            ));
        }
        Ok(Signal::none())
    }

    fn debug(&self) -> Vec<(&str, String)> {
        <dyn StatefulAction>::debug(self)
            .into_iter()
            .chain([("group", format!("{:?}", self.group))])
            .collect()
    }
}