WIP: Basic Routine Functionality #40

Draft
modulatingforce wants to merge 23 commits from routines-functionality into master
7 changed files with 1649 additions and 24 deletions

View file

@ -5,40 +5,83 @@ use tokio::sync::RwLock;
use crate::core::botinstance::BotInstance;
use super::{botmodules::{BotAction, BotModule}, identity::ChatBadge};
use super::{botinstance::Channel, botmodules::{BotAction, BotModule, Routine}, identity::ChatBadge};
pub type BotAR = Arc<RwLock<BotInstance>>;
pub type ActAR = Arc<RwLock<BotAction>>;
pub type RoutineAR = Arc<RwLock<Routine>>;
#[derive(Clone)]
pub struct ExecBodyParams {
pub bot : BotAR,
pub msg : PrivmsgMessage,
pub parent_act : ActAR ,
pub parent_act : Option<ActAR> ,
pub curr_act : ActAR ,
}
impl ExecBodyParams {
pub async fn get_parent_module(&self) -> Option<BotModule> {
// pub async fn get_parent_module(&self) -> Option<BotModule> {
// pub async fn get_parent_module(&self) -> BotModule {
pub async fn get_module(&self) -> BotModule {
let parent_act = Arc::clone(&self.parent_act);
let parent_act_lock = parent_act.read().await;
let curr_act = Arc::clone(&self.curr_act);
let parent_act_lock = curr_act.read().await;
let act = &(*parent_act_lock);
match act {
BotAction::C(c) => {
let temp = c.module.clone();
Some(temp)
// let temp = c.module.clone();
// Some(temp)
c.module.clone()
},
BotAction::L(l) => {
let temp = l.module.clone();
Some(temp)
// let temp = l.module.clone();
// Some(temp)
l.module.clone()
},
_ => None
BotAction::R(r) => {
// let temp = r.module.clone();
// Some(temp)
r.read().await.module.clone()
}
// _ => None
}
}
pub async fn get_channel(&self) -> Option<Channel> {
// THIS IS INCORRECT - BELOW MAY BE PULLING THE PARENT BOTACTION
// NOT THE CURRENT BOT ACTION
let curr_act = Arc::clone(&self.curr_act);
let parent_act_lock = curr_act.read().await;
let act = &(*parent_act_lock);
match act {
BotAction::C(_) => {
// let temp = c.module.clone();
// Some(temp)
Some(Channel(self.msg.channel_login.clone()))
},
BotAction::L(_) => {
// let temp = l.module.clone();
// Some(temp)
// l.module.clone()
Some(Channel(self.msg.channel_login.clone()))
},
BotAction::R(r) => {
// let temp = r.module.clone();
// Some(temp)
Some(r.read().await.channel.clone())
}
// _ => None
}
}
pub fn get_sender(&self) -> String {
self.msg.sender.name.clone()
}

View file

@ -404,7 +404,8 @@ impl BotInstance {
let params = ExecBodyParams {
bot : Arc::clone(&bot),
msg : (*msg).clone(),
parent_act : Arc::clone(&act_clone),
parent_act : None,
curr_act : Arc::clone(&act_clone),
};
// When sending a BotMsgTypeNotif, send_botmsg does Roles related validation as required
@ -461,7 +462,8 @@ impl BotInstance {
let params = ExecBodyParams {
bot : Arc::clone(&bot),
msg : (*msg).clone(),
parent_act : Arc::clone(&act_clone),
parent_act : None,
curr_act : Arc::clone(&act_clone),
};
@ -491,7 +493,8 @@ impl BotInstance {
let params = ExecBodyParams {
bot : Arc::clone(&bot),
msg : (*msg).clone(),
parent_act : Arc::clone(&act_clone),
parent_act : None,
curr_act : Arc::clone(&act_clone),
};
@ -516,7 +519,8 @@ impl BotInstance {
c.execute(ExecBodyParams {
bot : a,
msg : msg.clone() ,
parent_act : Arc::clone(&act_clone),
parent_act : None,
curr_act : Arc::clone(&act_clone),
}).await;
botlog::trace(
@ -564,7 +568,8 @@ impl BotInstance {
l.execute(ExecBodyParams {
bot : a,
msg : msg.clone() ,
parent_act : Arc::clone(&act_clone),
parent_act : None,
curr_act : Arc::clone(&act_clone),
} ).await;
}

View file

@ -25,14 +25,25 @@ const OF_CMD_CHANNEL:Channel = Channel(String::new());
use core::panic;
use std::borrow::Borrow;
use std::borrow::BorrowMut;
use std::collections::HashMap;
use std::ops::DerefMut;
use std::sync::Arc;
use casual_logger::Log;
use chrono::DateTime;
// use chrono::Duration;
use chrono::Local;
use chrono::OutOfRangeError;
use tokio::sync::RwLock;
use async_trait::async_trait;
use tokio::task::JoinHandle;
use tokio::time::Instant;
use tokio::time::{sleep, Duration};
use crate::core::bot_actions::actions_util;
use crate::core::bot_actions::ExecBodyParams;
@ -44,6 +55,8 @@ use crate::core::bot_actions;
use std::hash::{Hash, Hasher};
use super::bot_actions::ActAR;
use super::bot_actions::RoutineAR;
use super::identity::ChatBadge;
@ -69,7 +82,6 @@ pub async fn init(mgr: Arc<ModulesManager>) {
// 2. Add the BotAction to ModulesManager
botc1.add_core_to_modmgr(Arc::clone(&mgr)).await;
// async fn cmd_enable(bot: BotAR, msg: PrivmsgMessage) {
async fn cmd_enable(params : ExecBodyParams) {
/*
There should be additional validation checks
@ -253,7 +265,6 @@ pub async fn init(mgr: Arc<ModulesManager>) {
// 2. Add the BotAction to ModulesManager
botc1.add_core_to_modmgr(Arc::clone(&mgr)).await;
// async fn cmd_disable(bot: BotAR, msg: PrivmsgMessage) {
async fn cmd_disable(params : ExecBodyParams) {
/*
There should be additional validation checks
@ -462,7 +473,7 @@ pub enum StatusType {
pub enum BotAction {
C(BotCommand),
L(Listener),
R(Routine),
R(Arc<RwLock<Routine>>),
}
impl BotAction {
@ -568,8 +579,895 @@ impl BotActionTrait for Listener {
}
}
#[derive(Debug)]
pub struct Routine {}
// #[derive(Debug, PartialEq, Eq, Hash, Clone)]
#[derive(Debug, PartialEq, Eq, Hash)]
pub enum RoutineAttr {
DelayedStart,
ScheduledStart(DateTime<Local>), // Scheduled Date (if any) after which, if not started, may trigger
LoopDuration(Duration), // How long to wait between iterations
LoopInfinitely,
RunOnce,
MaxTimeThreshold(DateTime<Local>), // DateTime after which, it will abort/cancel or stop
MaxIterations(i64),
}
/*
a Routine can be given different combinations of the above, but business logic may validate
these at Routine construction
For example, a Routine could have the following characteristics
- DelayedStart (so it skips the first iteration)
- ScheduledStart(DateTime<Local>) - With a Start Date-Time for the first iteration to trigger
- LoopDuration(Duration) - How long to wait between iterations
The above without any other thresholds would loop infinitely with the above characteristics
Another example,
- LoopDuration(Duration) - How long to wait between iterations
- MaxTimeThreshold(DateTime<Local>)
- MaxIterations(i64)
The above has thresholds , so if either are reached, it would abort/cancel or stop . Since there is no
ScheduledStart, the routine would have to be started manually elsewhere
Another example ,
- (no RoutineAttr)
The above would only run once, and only when the Start() is called
*/
// For some key statuses and in particular Stopping to Gracefully stop
pub enum RoutineSignal {
Stopping, // Gracefully Stopping
Stopped, // When cancelling or aborting, this also is represented by Stopped
Started, // After Routine Started
NotStarted,
}
// #[derive(Debug)]
pub struct Routine {
pub name : String ,
pub module : BotModule , // from() can determine this if passed parents_params
pub channel : Channel , // Requiring some channel context
exec_body: bot_actions::actions_util::ExecBody,
pub parent_params : ExecBodyParams ,
pub join_handle : Option<Arc<RwLock<JoinHandle<RoutineAR>>>> ,
start_time : Option<DateTime<Local>> ,
pub complete_iterations : i64 ,
pub remaining_iterations : Option<i64> ,
routine_attr : Vec<RoutineAttr> ,
pub internal_signal : RoutineSignal ,
pub self_routine_ar : Option<RoutineAR> ,
pub self_act_ar : Option<ActAR> ,
}
impl Routine {
// // pub fn set
// // pub async fn refresh_self_ref(self) {
// pub async fn refresh_routine_internal(routine_ar : RoutineAR)
// {
// /*
// Execute after the Routine is constructed
// - If not, a start() will also call this
// */
// // 1. Update the self reference to itself
// let mut mut_lock = routine_ar.write().await;
// mut_lock.self_routine_ar = Some(routine_ar.clone());
// // 2. Update the current self_act_ar
// mut_lock.self_act_ar = Some(Arc::new(RwLock::new(BotAction::R(routine_ar.clone()))));
// }
pub async fn validate_attr(routine_attr : &Vec<RoutineAttr>)
-> Result<String,String>
// [ ] => 03.27 - REVIEW FOR COMPLETION
{
/*
GENERAL LOGIC :
[x] 1. Define RoutineAttr in a broad level that are known to be implented or are work in progress
[x] 2. Built in Logic will check these vectors, and return if Not Implemented
[x] 3. If Implemented , then there are additional internal validation based on combination done later
*/
// [x] 1. Define RoutineAttr in a broad level that are known to be implented or are work in progress
// adjust the below for those that are work in progress or that are implemented
// - This will allow other functions to validate that it is implemented
// // WORK IN PROGRESS VECTOR - Vec<$RoutineAttr>
// let wip_attr:Vec<RoutineAttr> = vec![
// RoutineAttr::DelayedStart,
// RoutineAttr::ScheduledStart(chrono::offset::Local::now()),
// RoutineAttr::LoopDuration(Duration::from_secs(1)),
// RoutineAttr::LoopInfinitely, // Note : There's no added implementation for this
// RoutineAttr::RunOnce,
// RoutineAttr::MaxTimeThreshold(chrono::offset::Local::now()),
// RoutineAttr::MaxIterations(1),
// ];
// let implemented_attr:Vec<RoutineAttr> = vec![
// ];
// [x] 2. Built in Logic will check these vectors, and return if Not Implemented
// let mut unimplemented = routine_attr.iter()
// .filter(|x| {
// let inx = x;
// wip_attr.iter().filter(|y| matches!(y,i if i == inx)).next().is_none()
// && implemented_attr.iter().filter(|y| matches!(y,i if i == inx)).next().is_none()
// }
// );
let mut attribute_supported = false;
for given_routine in routine_attr {
// if !matches!(given_routine,RoutineAttr::DelayedStart)
// && !matches!(given_routine,RoutineAttr::ScheduledStart(_))
// && !matches!(given_routine,RoutineAttr::LoopDuration(_))
// && !matches!(given_routine,RoutineAttr::LoopInfinitely)
// && !matches!(given_routine,RoutineAttr::RunOnce)
// && !matches!(given_routine,RoutineAttr::MaxTimeThreshold(_))
// && !matches!(given_routine,RoutineAttr::MaxIterations(_))
if matches!(given_routine,RoutineAttr::DelayedStart)
|| matches!(given_routine,RoutineAttr::ScheduledStart(_))
|| matches!(given_routine,RoutineAttr::LoopDuration(_))
|| matches!(given_routine,RoutineAttr::LoopInfinitely)
|| matches!(given_routine,RoutineAttr::RunOnce)
|| matches!(given_routine,RoutineAttr::MaxTimeThreshold(_))
|| matches!(given_routine,RoutineAttr::MaxIterations(_))
{
attribute_supported = true;
}
};
if !attribute_supported {
botlog::trace(
"[ERROR][Routine Feature NOT IMPLEMENTED]",
Some("Routine > Validate_attr()".to_string()),
None,
);
Log::flush();
botlog::trace(
format!(
"[ERROR][Routine Feature NOT IMPLEMENTED] > Problem Routine - {:?}"
,routine_attr).as_str(),
Some("Routine > Validate_attr()".to_string()),
None,
);
Log::flush();
return Err("NOT IMPLEMENTED".to_string());
}
// if unimplemented.next().is_some() {
// botlog::trace(
// "[ERROR][Routine Feature NOT IMPLEMENTED]",
// Some("Routine > Validate_attr()".to_string()),
// None,
// );
// Log::flush();
// return Err("NOT IMPLEMENTED".to_string());
// }
// [x] 3. If Implemented , then there are additional internal validation based on combination done later to ERR
// Below ensures a routine_attr containing LoopInfinitely does not contain conflicit attributes
if routine_attr.contains(&RoutineAttr::LoopInfinitely) &&
( routine_attr.contains(&RoutineAttr::RunOnce)
|| routine_attr.iter().filter(|y| matches!(y,&&RoutineAttr::MaxIterations(_))).next().is_some()
|| routine_attr.iter().filter(|y| matches!(y,&&RoutineAttr::MaxTimeThreshold(_))).next().is_some()
)
{
return Err("Conflicting Routine Attributes".to_string())
}
// [x] if there is no RunOnce, there must be a LoopDuration
if !routine_attr.contains(&RoutineAttr::RunOnce)
&& routine_attr.iter()
.filter(|x| matches!(x,&&RoutineAttr::LoopDuration(_)) )
.next().is_none()
{
return Err("LoopDuration is required if not RunOnce".to_string());
}
// [x] Err if DelayedStart but no LoopDuration
if routine_attr.contains(&RoutineAttr::DelayedStart) &&
routine_attr.iter()
.filter(|x| matches!(x,&&RoutineAttr::LoopDuration(_)) )
.next().is_none()
{
return Err("DelayedStart must include a LoopDuration".to_string())
}
// [x] 4. If all other failure checks above works, ensure one more time that the atstribute is implemented
// - If not, routine NOT IMPLEMENTED error
// if routine_attr.iter()
// .filter(|x| {
// let inx = x;
// wip_attr.iter().filter(|y| matches!(y,i if i == inx)).next().is_none()
// || implemented_attr.iter().filter(|y| matches!(y,i if i == inx)).next().is_none()
// })
// .next()
// .is_none()
// {
botlog::trace(
"[OK][Implemented & Validated]",
Some("Routine > Validate_attr()".to_string()),
None,
);
Log::flush();
Ok("Implemented & Validated".to_string())
}
pub async fn validate_self_attr(self)
-> Result<String,String>
// [x] => 03.27 - COMPLETED
{
Routine::validate_attr(&self.routine_attr).await
}
// Constructor
pub async fn from(
name : String ,
module : BotModule ,
channel : Channel,
routine_attr : Vec<RoutineAttr> ,
exec_body : bot_actions::actions_util::ExecBody ,
parent_params : ExecBodyParams
) -> Result<
Arc<RwLock<Routine>>,
String
>
// [x] => 03.27 - COMPLETED
{
Routine::validate_attr(&routine_attr).await?;
let routine_ar = Arc::new(RwLock::new(Routine {
name ,
module ,
channel ,
exec_body ,
parent_params ,
join_handle : None ,
start_time : None ,
complete_iterations : 0 ,
remaining_iterations : None ,
routine_attr : routine_attr ,
internal_signal : RoutineSignal::NotStarted ,
self_routine_ar : None ,
self_act_ar : None ,
}));
let mut mut_lock = routine_ar.write().await;
mut_lock.self_routine_ar = Some(routine_ar.clone());
// 2. Update the current self_act_ar
mut_lock.self_act_ar = Some(Arc::new(RwLock::new(BotAction::R(routine_ar.clone()))));
Ok(routine_ar.clone())
// return Ok(Arc::new(RwLock::new(Routine {
// name ,
// module ,
// channel ,
// exec_body ,
// parent_params ,
// join_handle : None ,
// start_time : None ,
// complete_iterations : 0 ,
// remaining_iterations : None ,
// routine_attr : routine_attr ,
// internal_signal : RoutineSignal::NotStarted ,
// self_routine_ar : None ,
// self_act_ar : None ,
// }))) ;
}
pub async fn start(
trg_routine_ar : Arc<RwLock<Routine>>
// ) -> Result<String,String>
) -> Result<Arc<RwLock<Routine>>,String>
// [ ] => 03.27 - REVIEW FOR COMPLETION
{
// // [x] Prep by updating it's own self reference
// Routine::refresh_routine_internal(trg_routine_ar.clone()).await;
// [x] Asyncio Spawn likely around here
// [x] & Assigns self.join_handle
/*
UMBRELLA ROUTINE LOGIC
1. Create a loop scenario based on routine_attr such as RunOnce
2. Run the loop depending on how the Routine is setup
START LOGIC :
1. Ideally validate the routineattr that they're not a problem scenario (However, this should have been validated At Setup)
a. Extra helper validation function though would help in case the attributes were changes between setup
2. Use these attributes only in Critical areas of the loop logic to determine changes in Loop logic
*/
// Use the following to stop the function from going any further if not implemented
Routine::validate_attr(&trg_routine_ar.read().await.routine_attr).await?;
// if !trg_routine_ar.read().await.routine_attr.contains(&RoutineAttr::RunOnce) {
// botlog::trace(
// format!(
// "[ERROR][Routine Feature NOT IMPLEMENTED] {} in {}",
// trg_routine_ar.read().await.name,
// trg_routine_ar.read().await.channel.0
// )
// .as_str(),
// Some(format!(
// "Routine > start() > (In Tokio Spawn) > {:?}",
// trg_routine_ar.read().await.module
// )),
// Some(&trg_routine_ar.read().await.parent_params.msg),
// );
// Log::flush();
// return Err("NOT IMPLEMENTED".to_string())
// }
let trg_routine_arout = Arc::clone(&trg_routine_ar);
botlog::trace(
"innerhelper() started",
Some(format!(
"Routine > start() > (In Tokio Spawn)",
)),
Some(&trg_routine_ar.read().await.parent_params.msg),
);
Log::flush();
// Spawn the task
let join_handle = tokio::spawn(async move {
botlog::trace(
">> Within Spawn",
Some(format!(
"Routine > start() > (In Tokio Spawn)",
)),
Some(&trg_routine_ar.read().await.parent_params.msg),
);
Log::flush();
// [x] If Scheduled Start or Delayed Start, Handle that first
fn duration_to_datetime(future_dt: DateTime<Local>) -> Result<Duration,OutOfRangeError>
{
(future_dt - chrono::offset::Local::now()).to_std()
}
let delayduration = {
let lock = trg_routine_ar.read().await;
let mut related_attrs = lock
.routine_attr.iter()
.filter(|x| matches!(x,&&RoutineAttr::DelayedStart) || matches!(x,&&RoutineAttr::ScheduledStart(_)) );
// match related_attrs.next() {
// }
async fn duration_from_attr(attr: &RoutineAttr,trg_routine_ar : RoutineAR) -> Option<Duration> {
// let duration_from_attr = async {
let lock = trg_routine_ar.read().await;
match attr {
RoutineAttr::ScheduledStart(dt) => {
if let Ok(dur) = duration_to_datetime(*dt) {
Some(dur)
} else { None }
},
RoutineAttr::DelayedStart => {
let mut loopdur_attr_iter = lock
.routine_attr.iter()
.filter(|x| matches!(x,&&RoutineAttr::LoopDuration(_)) );
if let Some(loopdur_attr) = loopdur_attr_iter.next() {
if let RoutineAttr::LoopDuration(dur) = loopdur_attr {
Some(*dur)
} else { None }
} else { None }
// None
},
_ => { None } // Handle no other combination
}
}
// The following is done twice just in case ScheduledStart and DelayedStart are defined
let delayduration01 = if let Some(attr) = related_attrs.next() {
duration_from_attr(attr, trg_routine_ar.clone()).await
} else { None };
let delayduration02 = if let Some(attr) = related_attrs.next() {
duration_from_attr(attr, trg_routine_ar.clone()).await
} else { None };
// if there is a 2nd related duration, pick the minimum, otherwise, pick the results of delayduration01
if delayduration02.is_some() {
Some(Duration::min(delayduration01.unwrap(),delayduration02.unwrap()))
} else { delayduration01 }
};
botlog::trace(
format!(
"[TRACE][Routine Processing] {} in {} > Delay Duration - {:?} ",
trg_routine_ar.read().await.name,
trg_routine_ar.read().await.channel.0 ,
delayduration ,
)
.as_str(),
Some(format!(
"Routine > start() > (In Tokio Spawn) > {:?}",
trg_routine_ar.read().await.module
)),
Some(&trg_routine_ar.read().await.parent_params.msg),
);
if let Some(dur) = delayduration {
sleep(dur).await;
}
{ // [x] Loop Initialization - Prior to Loop that calls Custom Routine Execution Body
let mut a = trg_routine_ar.write().await;
a.start_time = Some(chrono::offset::Local::now());
if let Some(&RoutineAttr::MaxIterations(iternum)) =
a.routine_attr.iter()
.filter(|x| matches!(x,RoutineAttr::MaxIterations(_)))
.next()
{
a.remaining_iterations = Some(iternum);
}
}
loop { // [x] Routine loop
// [x] execution body
// trg_routine_ar.read().await.loopbody().await;
{
trg_routine_ar.write().await.loopbody().await;
}
{ // [x] End of Loop iteration
let mut a = trg_routine_ar.write().await;
// [x] Check if Gracefully Stopping Signal was sent
if matches!(a.internal_signal,RoutineSignal::Stopping) {
a.internal_signal = RoutineSignal::Stopped;
break ;
}
// [x] Check and adjust iterations
a.complete_iterations += 1;
if let Some(i) = a.remaining_iterations {
if i > 0 { a.remaining_iterations = Some(i-1) ; }
else { break ; } // if remaining iterations is 0, exit
}
}
// [x] End of Loop Validation
// These generally may include routine_attr related checks to , for example, break out of the loop
if trg_routine_ar.read().await.routine_attr.contains(&RoutineAttr::RunOnce) {
if trg_routine_ar.read().await.complete_iterations > 0 { break; }
}
// return if max time has passed
if let Some(&RoutineAttr::MaxTimeThreshold(dt)) = trg_routine_ar.read().await.routine_attr.iter()
.filter(|x| matches!(x,&&RoutineAttr::MaxTimeThreshold(_)) )
.next() {
if chrono::offset::Local::now() > dt { break; }
}
// [x] Checks for Loop duration to sleep
if let Some(&RoutineAttr::LoopDuration(dur)) = trg_routine_ar.read().await.routine_attr.iter()
.filter(|x| matches!(x,&&RoutineAttr::LoopDuration(_)) )
.next()
{
sleep(dur).await;
};
}
botlog::trace(
format!(
"[TRACE][Routine Completed] {} in {}",
trg_routine_ar.read().await.name,
trg_routine_ar.read().await.channel.0
)
.as_str(),
Some(format!(
"Routine > start() > (In Tokio Spawn) > {:?}",
trg_routine_ar.read().await.module
)),
Some(&trg_routine_ar.read().await.parent_params.msg),
);
botlog::trace(
format!(
"[TRACE][Routine Completed][Routine Header Test] {} in {} > Completed Iterations : {}",
trg_routine_ar.read().await.name,
trg_routine_ar.read().await.channel.0 ,
trg_routine_ar.read().await.complete_iterations,
)
.as_str(),
Some(format!(
"Routine > start() > (In Tokio Spawn) > {:?}",
trg_routine_ar.read().await.module
)),
Some(&trg_routine_ar.read().await.parent_params.msg),
);
Log::flush();
trg_routine_ar
});
{ // Recommendation to ensure a clean update is to use one write() lock that was awaited
// - We can isolate the write lock by ensuring it's in it's own block
let mut lock = trg_routine_arout.write().await;
lock.join_handle = Some(Arc::new(RwLock::new(join_handle)));
lock.internal_signal = RoutineSignal::Started;
}
trg_routine_arout.write().await.internal_signal = RoutineSignal::Started;
return Ok(trg_routine_arout);
}
async fn loopbody(&mut self)
// [x] => 03.27 - COMPLETED
{
botlog::trace(
"loopbody() started",
Some(format!(
"Routine > start() > (During Tokio Spawn) > Execution body",
)),
None,
);
Log::flush();
let self_ar = Arc::new(RwLock::new(self));
{
let mut mutlock = self_ar.write().await;
mutlock.parent_params.parent_act = Some(mutlock.parent_params.curr_act.clone());
mutlock.parent_params.curr_act = mutlock.self_act_ar.to_owned().unwrap();
}
(self_ar.read().await.exec_body)(
self_ar.read().await.parent_params.clone()
).await;
// (self.exec_body)(
// self.parent_params.clone()
// ).await;
}
pub async fn stop(&mut self) -> Result<String,String>
// [ ] => 03.27 - REVIEW FOR COMPLETION
{
let self_rw = Arc::new(RwLock::new(self));
{
let mut self_lock = self_rw.write().await;
self_lock.internal_signal = RoutineSignal::Stopping;
}
let self_lock = self_rw.read().await;
botlog::trace(
format!(
"[ROUTINE][Sent Gracefully Stop Signal] {} in {}",
self_lock.name,self_lock.channel.0
)
.as_str(),
Some(format!(
"Routine > stop() > {:?}",
self_lock.module
)),
Some(&self_lock.parent_params.msg),
);
Log::flush();
Ok("Sent Gracefully Stop Signal".to_string())
// botlog::trace(
// format!(
// "[ERROR][Routine NOT IMPLEMENTED] {} in {}",
// self_lock.name,self_lock.channel.0
// )
// .as_str(),
// Some(format!(
// "Routine > start() > (In Tokio Spawn) > {:?}",
// self_lock.module
// )),
// Some(&self_lock.parent_params.msg),
// );
// Log::flush();
// Err("NOT IMPLEMENTED".to_string())
}
pub async fn cancel(&mut self) -> Result<String,String>
// [ ] => 03.27 - REVIEW FOR COMPLETION
{
// [ ] Likely calls abort()
// Related :
// https://docs.rs/tokio/latest/tokio/task/struct.JoinHandle.html#method.abort
let self_rw = Arc::new(RwLock::new(self));
let self_lock = self_rw.read().await;
match &self_lock.join_handle {
None => return Err("No Join Handle on the Routine to Cancel".to_string()),
Some(a) => {
a.read().await.abort();
{
let mut lock_mut = self_rw.write().await;
lock_mut.internal_signal = RoutineSignal::Stopped;
}
}
}
botlog::trace(
format!(
"[ROUTINE][Cancelled Routine] {} in {}",
self_lock.name,self_lock.channel.0
)
.as_str(),
Some(format!(
"Routine > cancel() > {:?}",
self_lock.module
)),
Some(&self_lock.parent_params.msg),
);
Log::flush();
Ok("Cancelled Successfully".to_string())
// botlog::trace(
// format!(
// "[ERROR][Routine NOT IMPLEMENTED] {} in {}",
// self_lock.name,self_lock.channel.0
// )
// .as_str(),
// Some(format!(
// "Routine > start() > (In Tokio Spawn) > {:?}",
// self_lock.module
// )),
// Some(&self_lock.parent_params.msg),
// );
// Log::flush();
// Err("NOT IMPLEMENTED".to_string())
}
pub async fn restart(
// &mut self,
self,
force : bool
) -> Result<String,String>
// [ ] => 03.27 - REVIEW FOR COMPLETION
{
// force flag aborts the routine immediately (like cancel())
let self_rw = Arc::new(RwLock::new(self));
if force
{
let mut self_lock = self_rw.write().await;
self_lock.cancel().await?;
} else {
let mut self_lock = self_rw.write().await;
self_lock.stop().await?;
}
Routine::start(self_rw.clone()).await?;
let self_lock = self_rw.read().await;
botlog::trace(
format!(
"[ROUTINE][Restarted Routine] {} in {}",
self_lock.name,self_lock.channel.0
)
.as_str(),
Some(format!(
"Routine > restart() > {:?}",
self_lock.module
)),
Some(&self_lock.parent_params.msg),
);
Log::flush();
Ok("Restarted successfully".to_string())
}
pub async fn change_channel(
&mut self,
channel : Channel
) -> Result<String,String>
// [ ] => 03.28 - REVIEW FOR COMPLETION
{
// [x] Think Ideally it should try to
// change the target channel of the
// internal process too if possible?
self.channel = channel;
let self_rw = Arc::new(RwLock::new(self));
let self_lock = self_rw.read().await;
botlog::trace(
format!(
"[ROUTINE][Change Channel] {} in {}",
self_lock.name,self_lock.channel.0
)
.as_str(),
Some(format!(
"Routine > restart() > {:?}",
self_lock.module
)),
Some(&self_lock.parent_params.msg),
);
Log::flush();
// Err("NOT IMPLEMENTED".to_string())
Ok("Changed Successfully".to_string())
}
pub async fn set_routine_attributes(
&mut self,
routine_attr : Vec<RoutineAttr>
) -> Result<String,String>
// [ ] => 03.27 - WIP - NOT IMPLEMENTED
{
// This is way to custom set attributes first
// They will Be Validated before being applied
// IDEALLY the routine also be restarted afterwards externally
Routine::validate_attr(&routine_attr).await?;
let self_rw = Arc::new(RwLock::new(self));
{
let mut self_lock = self_rw.write().await;
self_lock.routine_attr = routine_attr;
}
// let self_rw = Arc::new(RwLock::new(self));
let self_lock = self_rw.read().await;
botlog::trace(
format!(
"[ROUTINE][Set Routine Attributes] {} in {}",
self_lock.name,self_lock.channel.0
)
.as_str(),
Some(format!(
"Routine > restart() > {:?}",
self_lock.module
)),
Some(&self_lock.parent_params.msg),
);
Log::flush();
Ok("Changed Successfully".to_string())
}
}
type StatusdbEntry = (ModGroup, Vec<StatusType>);
type ModuleActions = Vec<Arc<RwLock<BotAction>>>;

View file

@ -101,14 +101,15 @@ impl Chat {
Some(&params.msg),
);
let parent_module = params.get_parent_module().await;
let parent_module = params.get_module().await;
let params_clone = params.clone();
let botclone = Arc::clone(&params_clone.bot);
let botlock = botclone.read().await;
let modmgr = Arc::clone(&botlock.botmodules);
let modstatus = (*modmgr).modstatus(
parent_module.clone().expect("ERROR - Expected a module"),
// parent_module.clone().expect("ERROR - Expected a module"),
parent_module.clone(),
Channel(channel_login.clone())
).await;
@ -180,7 +181,8 @@ impl Chat {
self.send_botmsg(BotMsgType::Notif(
format!("uuh {:?} is disabled on {} : {:?}",
parent_module.clone().unwrap(),
// parent_module.clone().unwrap(),
parent_module.clone(),
channel_login.clone(),
lvl
),

View file

@ -553,7 +553,16 @@ async fn getroles(params : ExecBodyParams) {
let arg1 = argv.next();
let targetuser = match arg1 {
None => return, // exit if no arguments
None => {
botlog::debug(
"Exittingcmd getroles - Invalid arguments ",
Some("identity.rs > init > getroles()".to_string()),
Some(&params.msg),
);
return
}, // exit if no arguments
Some(arg) => arg,
};

View file

@ -12,6 +12,7 @@ pub use crate::core::botmodules::ModulesManager;
mod experiment001;
mod experiment002;
mod experiment003;
// [ ] init() function that accepts bot instance - this is passed to init() on submodules
@ -21,4 +22,5 @@ pub async fn init(mgr: Arc<ModulesManager>) {
experiment001::init(Arc::clone(&mgr)).await;
experiment002::init(Arc::clone(&mgr)).await;
experiment003::init(Arc::clone(&mgr)).await;
}

View file

@ -0,0 +1,666 @@
/*
Custom Modules -
Usage :
[ ] within the file's init(), define BotActions & Load them into the ModulesManager
[ ] Define Execution Bodies for these BotActions
[ ] Afterwards, add the following to parent modules.rs file
- mod <modulename>;
- within init(), <modulename>::init(mgr).await
*/
const OF_CMD_CHANNEL:Channel = Channel(String::new());
use casual_logger::Log;
use rand::Rng;
use rand::thread_rng;
use rand::rngs::StdRng;
use rand::seq::SliceRandom;
use tokio::sync::RwLock;
use std::borrow::Borrow;
use std::borrow::BorrowMut;
use std::sync::Arc;
use crate::core::bot_actions::ExecBodyParams;
use crate::core::botinstance::Channel;
use crate::core::botlog;
use crate::core::bot_actions::actions_util;
use crate::core::botmodules::{BotAction, BotActionTrait, BotCommand, BotModule, Listener, ModulesManager, Routine, RoutineAttr};
use crate::core::identity::UserRole::*;
use tokio::time::{sleep, Duration};
pub async fn init(mgr: Arc<ModulesManager>) {
// 1. Define the BotAction
let botc1 = BotCommand {
module: BotModule(String::from("experiments003")),
command: String::from("test3"), // command call name
alias: vec![], // String of alternative names
exec_body: actions_util::asyncbox(test3_body),
help: String::from("Test Command tester"),
required_roles: vec![
BotAdmin,
Mod(OF_CMD_CHANNEL),
],
};
// 2. Add the BotAction to ModulesManager
botc1.add_to_modmgr(Arc::clone(&mgr)).await;
// 1. Define the BotAction
let botc1 = BotCommand {
module: BotModule(String::from("experiments003")),
command: String::from("countdown"), // command call name
alias: vec![], // String of alternative names
exec_body: actions_util::asyncbox(countdown_chnl_v1),
help: String::from("Test Command tester"),
required_roles: vec![
BotAdmin,
Mod(OF_CMD_CHANNEL),
],
};
// 2. Add the BotAction to ModulesManager
botc1.add_to_modmgr(Arc::clone(&mgr)).await;
}
async fn countdown_chnl_v1(params : ExecBodyParams) {
botlog::debug(
"[CHILDFN] countdown_chnl() triggered!",
Some("Experiments003 > countdown_chnl()".to_string()),
Some(&params.msg),
);
/*
create a fun countdown BotCommand that allows an Elevated Chatter to
-a add channels to target a routine message
-start to start the routine with an input String, that sends a number
of messages to the targeted channels with a countdown, until it
reaches 0 when it sends a cute or funny message
NOTE : At the moment, I don't have customizable persistence, so I would just use
counters from the Routine itself
*/
/*
Because of some bot core features are not available, v1.0 of this could be :
[x] 1. Create a Routine & start a routine
[x] 2. Have the routine go through each joined channel randomly and countdown
[x] 3. At the end, say "0, I love you uwu~" in the last chosen channel
*/
/*
Usage => 03.28 - skipping arguments as functinoality isn't enhanced
-a <channel> => 03.28 - Not sure if this is possible at the moment?
-start
-stop => 03.28 - Not sure if this is possible at the moment?
*/
/*
[ ] Functional Use Case
1. -a <channel> adds targetted channels
*/
// [-] Unwraps arguments from message
// let (arg1, arg2) = {
// let mut argv = params.msg.message_text.split(' ');
// argv.next(); // Skip the command name
// let arg1 = argv.next();
// let arg2 = argv.next();
// (arg1, arg2)
// };
// [ ] 1. Create a Routine & start a routine
// let parentmodule = params.get_parent_module().await;
let module = params.get_module().await;
let channel = params.get_channel().await;
let routine_attr = vec![
// RoutineAttr::RunOnce
RoutineAttr::MaxIterations(5),
RoutineAttr::LoopDuration(Duration::from_secs(1))
];
// let exec_body = actions_util::asyncbox(rtestbody);
let exec_body = actions_util::asyncbox(innertester); // <-- 03.27 - when below is uncommented, this is throwing an issue
async fn innertester(params : ExecBodyParams) {
{
let curract_guard = params.curr_act.read().await;
// let logmsg_botact = match *params.curr_act.read().await {
let logmsg_botact = match *curract_guard {
BotAction::C(_) => "command",
BotAction::R(_) => "routine",
BotAction::L(_) => "listener",
} ;
botlog::trace(
format!("Params > Curr_act type : {:?}", logmsg_botact).as_str(),
Some("Experiments003 > countdown_chnl()".to_string()),
Some(&params.msg),
);
Log::flush();
}
{
let bot = Arc::clone(&params.bot);
let botlock = bot.read().await;
let curract_guard = params.curr_act.write().await;
// let routine_lock = arr.write().await;
if let BotAction::R(arr) = &*curract_guard {
// if let BotAction::R(arr) = &*params.curr_act.read().await {
botlog::trace(
"Before loading remaining iterations",
Some("Experiments003 > countdown_chnl()".to_string()),
None,
);
Log::flush();
// let iterleft = arr.read().await.remaining_iterations.unwrap_or(0);
// // let iterleft = if arr.read().await.remaining_iterations.is_none() { 0i64 }
// // else { arr.read().await.remaining_iterations.unwrap() };
// let iterleft = match arr.read().await.remaining_iterations {
// None => 0,
// Some(a) => a,
// };
// let routine_lock = arr.read().await;
// if let Some(a) = routine_lock.remaining_iterations.clone() {
// println!("Remaining iterations > {}",a)
// }
{
let routine_lock = arr.write().await;
let a = routine_lock.remaining_iterations;
println!("remaining iterations : {:?}", a);
}
botlog::trace(
"after loading remaining iterations",
Some("Experiments003 > countdown_chnl()".to_string()),
None,
);
Log::flush();
// [ ] get joined channels
let joinedchannels = botlock.bot_channels.clone();
fn pick_a_channel(chnlvec : Vec<Channel>) -> Channel {
botlog::trace(
"In Pick_a_Channel()",
Some("Experiments003 > countdown_chnl()".to_string()),
None,
);
Log::flush();
// More Information : https://docs.rs/rand/0.7.2/rand/seq/trait.SliceRandom.html#tymethod.choose
let mut rng = thread_rng();
// let joinedchannels = botlock.bot_channels.clone();
(*chnlvec.choose(&mut rng).unwrap()).clone()
}
let chosen_channel = pick_a_channel(joinedchannels);
botlog::trace(
format!("Picked a channel: {:?}", chosen_channel).as_str(),
Some("Experiments003 > countdown_chnl()".to_string()),
Some(&params.msg),
);
Log::flush();
// let outmsg = if iterleft == 1 {
// format!("{} I love you uwu~",iterleft)
// } else { format!("{}",iterleft) };
// botlock.botmgrs.chat
// .say(
// // joinedchannels.choose(&mut rng).unwrap().0.clone(),
// chosen_channel.0.clone(),
// outmsg,
// params.clone()
// ).await;
}
}
}
// [ ] setup the routine
if let Ok(newr) = Routine::from(
"Routine Test".to_string(),
module,
channel.unwrap(),
routine_attr,
exec_body,
params.clone()
).await {
let newr_ar = newr.clone();
// [ ] start the routine
if let Ok(_) = Routine::start(newr_ar.clone()).await {
botlog::debug(
"Successfully started",
Some("experiment003 > countdown_chnl()".to_string()),
Some(&params.msg),
);
Log::flush();
let bot = Arc::clone(&params.bot);
let botlock = bot.read().await;
// uses chat.say_in_reply_to() for the bot controls for messages
botlock
.botmgrs
.chat
.say_in_reply_to(
&params.msg,
"Started Routine!".to_string(),
params.clone()
).await;
// let jhandle = newr.clone().read().await.join_handle.clone().unwrap();
// let a = jhandle.write().await;
// a.
// sleep(Duration::from_secs(300)).await;
}
}
// botlock
// .botmgrs
// .chat
// .say_in_reply_to(
// &params.msg,
// format!("{:?}",),
// params.clone()
// ).await;
}
async fn test3_body(params : ExecBodyParams) {
// println!("testy triggered!"); // NOTE : This test function intends to print (e.g., to stdout) at fn call
botlog::debug(
"testy triggered!",
Some("Experiments003 > test3 command body".to_string()),
Some(&params.msg),
);
/*
Test Routine Start() by :
1. In this single exec body , create a Routine
2. Create a Routine Execution Body
3. Pass the Execution Body & Routine Attributes to create the Routine
4. Start the Routine
5. For RunOnce , we should see it only trigger once, and then complete in the logs
*/
// [x] Get the module from params
// let parentmodule = params.get_parent_module().await;
let module = params.get_module().await;
let channel = params.get_channel().await;
let routine_attr = vec![
RoutineAttr::RunOnce
];
// let exec_body = actions_util::asyncbox(rtestbody);
let exec_body = actions_util::asyncbox(rtestbody); // <-- 03.27 - when below is uncommented, this is throwing an issue
// let parent_params = params.clone();
// let params_clone = params.clone();
async fn rtestbody(params : ExecBodyParams) {
let guard = params.curr_act.read().await;
{
let logmsg_botact = match *guard {
BotAction::C(_) => "command",
BotAction::R(_) => "routine",
BotAction::L(_) => "listener",
} ;
botlog::trace(
format!("Params > Curr_act type : {:?}", logmsg_botact).as_str(),
Some("Experiments003 > test3 command body".to_string()),
Some(&params.msg),
);
Log::flush();
}
{
let logmsg_botact = match *guard {
BotAction::C(_) => "command 2",
BotAction::R(_) => "routine 2",
BotAction::L(_) => "listener 2",
} ;
botlog::trace(
format!("Params > Curr_act type : {:?}", logmsg_botact).as_str(),
Some("Experiments003 > test3 command body".to_string()),
Some(&params.msg),
);
Log::flush();
}
{
println!("Critical code area start"); // <= 03.29 - This is printed
if let BotAction::R(c) = &*guard {
println!("{:?}",c.read().await.channel);
}
println!("Critical code area end"); // <= 03.29 - ISSUE This is NOT printed
Review

ISSUE . The defined Routine's Custom Execution Body is being reached (so the custom fn is being triggered as expected); However, I believe at L413 where c.read().await.channel , it never goes beyond that point within the same Custom fn body

Even if the bot continues to be responsive (i.e., the main bot continues to run), the rest of the routine is not. I believe this is suggesting there's a lock with one of the objects we're introducing with this feature . In the above line, I believe the lock involves ExecBodyParam's curr_act value

**_ISSUE ._** The defined Routine's Custom Execution Body is being reached (so the custom `fn` is being triggered as expected); However, I believe at L413 where `c.read().await.channel` , it never goes beyond that point within the same Custom `fn` body Even if the bot continues to be responsive (i.e., the `main` bot continues to run), the rest of the routine is not. I *believe* this is suggesting there's a lock with one of the objects we're introducing with this feature . In the above line, I believe the lock involves `ExecBodyParam`'s `curr_act` value
// if let BotAction::R(arr) = &*params.curr_act.read().await {
// for curriter in 0..5 {
// println!("tester - Routine - Completed Iterations : {}",
// arr.read().await.complete_iterations);
// println!("tester - Custom Loop - Completed Iterations : {}",
// curriter);
// sleep(Duration::from_secs_f64(0.5)).await;
// }
// }
}
}
botlog::debug(
format!("RTESTBODY : module - {:?} ; channel - {:?}",
module,channel
).as_str(),
Some("experiment003 > test3_body".to_string()),
Some(&params.msg),
);
let a = Routine::from(
"Routine Test".to_string(),
module,
channel.unwrap(),
routine_attr,
exec_body,
params.clone()
).await;
if let Ok(newr) = a {
// NOTE : The below is a "Playing aound" feature
// In the below, we're unnecessarily adjusting the ExecBodyParams of the parent
// To get the reference to the Routine or the BotAction there are now refernces to itself
// [ ] before execute , be sure to adjust curr_act
// let mut params_mut = params;
let newr_ar = newr.clone();
// params_mut.curr_act = Arc::new(RwLock::new(
// BotAction::R(newr_ar.clone())
// ));
// {
// newr_ar.write().await.parent_params = params_mut.clone();
// }
let rslt = Routine::start(newr_ar.clone()).await;
// let rslt = newr_ar.read().await.start().await;
let rsltstr = match rslt {
Ok(_) => "successful".to_string(),
Err(a) => a,
};
botlog::debug(
format!("TEST3_BODY RESULT : {:?}",
rsltstr
).as_str(),
Some("experiment003 > test3_body".to_string()),
Some(&params.msg),
);
Log::flush();
let bot = Arc::clone(&params.bot);
let botlock = bot.read().await;
// uses chat.say_in_reply_to() for the bot controls for messages
botlock
.botmgrs
.chat
.say_in_reply_to(
&params.msg,
format!("Routine Result : {:?}",rsltstr),
params.clone()
).await;
// [x] Will not be handling JoinHandles here . If immediate abort() handling is required, below is an example that works
/*
let a = newr.clone().read().await.join_handle.clone();
match a {
Some(b) => {
b.read().await.borrow().abort(); // [x] <-- This aborts if wanting to abort immediately
//()
},
None => (),
}
*/
}
Log::flush();
}
async fn good_girl(params : ExecBodyParams) {
// [ ] Uses gen_ratio() to output bool based on a ratio probability .
// - For example gen_ratio(2,3) is 2 out of 3 or 0.67% (numerator,denomitator)
// - More Info : https://rust-random.github.io/rand/rand/trait.Rng.html#method.gen_ratio
if params.msg.sender.name.to_lowercase() == "ModulatingForce".to_lowercase()
|| params.msg.sender.name.to_lowercase() == "mzNToRi".to_lowercase()
{
botlog::debug(
"Good Girl Detected > Pausechamp",
Some("experiments > goodgirl()".to_string()),
Some(&params.msg),
);
let rollwin = rand::thread_rng().gen_ratio(1, 10);
if rollwin {
botlog::debug(
"Oh that's a good girl!",
Some("experiments > goodgirl()".to_string()),
Some(&params.msg),
);
let bot = Arc::clone(&params.bot);
let botlock = bot.read().await;
// uses chat.say_in_reply_to() for the bot controls for messages
botlock
.botmgrs
.chat
.say_in_reply_to(
&params.msg,
String::from("GoodGirl xdd "),
params.clone()
).await;
}
}
}
async fn testy(params : ExecBodyParams) {
println!("testy triggered!"); // NOTE : This test function intends to print (e.g., to stdout) at fn call
botlog::debug(
"testy triggered!",
Some("experiments > testy()".to_string()),
Some(&params.msg),
);
}
async fn babygirl(params : ExecBodyParams) {
println!("babygirl triggered!"); // NOTE : This test function intends to print (e.g., to stdout) at fn call
botlog::debug(
"babygirl triggered!",
Some("experiments > babygirl()".to_string()),
Some(&params.msg),
);
let bot = Arc::clone(&params.bot);
let botlock = bot.read().await;
botlock
.botmgrs
.chat
.say_in_reply_to(
&params.msg,
String::from("16:13 notohh: cafdk"),
params.clone()
).await;
sleep(Duration::from_secs_f64(0.5)).await;
botlock
.botmgrs
.chat
.say_in_reply_to(
&params.msg,
String::from("16:13 notohh: have fun eating princess"),
params.clone()
).await;
sleep(Duration::from_secs_f64(2.0)).await;
botlock
.botmgrs
.chat
.say_in_reply_to(
&params.msg,
String::from("16:13 notohh: baby girl"),
params.clone()
).await;
}
async fn routinelike(params : ExecBodyParams) {
println!("routinelike triggered!"); // NOTE : This test function intends to print (e.g., to stdout) at fn call
botlog::debug(
"routinelike triggered!",
Some("experiments > routinelike()".to_string()),
Some(&params.msg),
);
// spawn an async block that runs independently from others
tokio::spawn( async {
for _ in 0..5 {
println!(">> Innterroutine triggered!");
sleep(Duration::from_secs_f64(5.0)).await;
}
}
);
// lines are executed after in conjunction to the spawn
}