[ENH] BotAction Execution Body implementation #7

Closed
opened 2023-12-24 19:24:41 -05:00 by modulatingforce · 5 comments

Enhance BotAction so reference to an async fn can execute as expected.

Expectation :

At the moment, I'm running into issues as I'm not able to store async fn in a struct like BotAction's exec_body ; because of this, I'm not able to independently call client module code.

Example problem case :
I get a Compilation Error for the implementation I tried

Given the following

in modules/experiments.rs


pub fn init(..) {

    Listener {
        module : BotModule(String::from("experiments 004")),
        name : String::from("GoodGirl Listener"),
        exec_body : good_girl,
        help : String::from("")
    }.add_to_modmgr(mgr);

}

async fn good_girl(bot:&mut BotInstance,msg:&PrivmsgMessage) {
    println!("In GoodGirl()");
    println!("(#{}) {}: {}", msg.channel_login, msg.sender.name, msg.message_text);

    if msg.sender.name == "ModulatingForce" && msg.message_text.contains("GoodGirl") {
        bot.chat.say_in_reply_to(msg,String::from("GoodGirl"));
    }

    ()
}


error[E0308]: mismatched types
  --> src\modules\experiments.rs:36:21
   |
36 |         exec_body : good_girl,
   |                     ^^^^^^^^^ expected fn pointer, found fn item
   |
   = note: expected fn pointer `for<'a, 'b> fn(&'a mut BotInstance, &'b PrivmsgMessage)`
                 found fn item `for<'a, 'b> fn(&'a mut BotInstance, &'b PrivmsgMessage) -> impl Future<Output = ()> {good_girl}`

For more information about this error, try `rustc --explain E0308`.

For the above, client module code would be experiments.rs . There may be an issue in an understanding the type I should be storing these (e.g., something with dyn keyword?)


This is relatively high priority. The understanding of this would help other BotAction types . Considering this almost a show-stopping knowledge gap

Enhance `BotAction` so reference to an `async fn` can execute as expected. Expectation : - Any changes or advice for Client / Module code ; ideally including supporting HOWTO documentation - Consider cleaning up existing Client Code documentation such as : https://git.flake.sh/modulatingforce/forcebot_rs/src/branch/main/src/help/HOWTO/Add_Modules_BotActions.md - During the bot loop, internal core code should be able to handle async functions that are passed from client code, such as `say_in_reply_to()` that might require async ? At the moment, I'm running into issues as I'm not able to store `async fn` in a struct like BotAction's exec_body ; because of this, I'm not able to independently call client module code. Example problem case : I get a Compilation Error for the implementation I tried Given the following in `modules/experiments.rs` ```rust pub fn init(..) { Listener { module : BotModule(String::from("experiments 004")), name : String::from("GoodGirl Listener"), exec_body : good_girl, help : String::from("") }.add_to_modmgr(mgr); } async fn good_girl(bot:&mut BotInstance,msg:&PrivmsgMessage) { println!("In GoodGirl()"); println!("(#{}) {}: {}", msg.channel_login, msg.sender.name, msg.message_text); if msg.sender.name == "ModulatingForce" && msg.message_text.contains("GoodGirl") { bot.chat.say_in_reply_to(msg,String::from("GoodGirl")); } () } ``` ```powershell error[E0308]: mismatched types --> src\modules\experiments.rs:36:21 | 36 | exec_body : good_girl, | ^^^^^^^^^ expected fn pointer, found fn item | = note: expected fn pointer `for<'a, 'b> fn(&'a mut BotInstance, &'b PrivmsgMessage)` found fn item `for<'a, 'b> fn(&'a mut BotInstance, &'b PrivmsgMessage) -> impl Future<Output = ()> {good_girl}` For more information about this error, try `rustc --explain E0308`. ``` --- For the above, client module code would be `experiments.rs` . There may be an issue in an understanding the type I should be storing these (e.g., something with `dyn` keyword?) --- This is relatively high priority. The understanding of this would help other `BotAction` types . Considering this almost a show-stopping knowledge gap
modulatingforce added this to the Prototype 1.0 milestone 2023-12-24 19:24:41 -05:00
modulatingforce added the
Priority
Medium
Kind/Feature
labels 2023-12-24 19:24:41 -05:00
modulatingforce added this to the Rust Learning project 2023-12-24 19:24:41 -05:00
Author
Owner

After testing in a sandbox project, I believe I'm able to get a working implementation that stores an async function into a struct

So it looks like a few things that I can keep in mind :

  • To avoid infinite or cyclical types, I should have Listener or ModuleManager interact with Chat commands through some intermediary like BotInstance, but I should not pass BotInstance to any of these, as BotInstance contains these structs

  • Define the custom function to be async, and has a signature similar to the following

async fn testfn2<F>(chat:Chat)

where

    F : std::future::Future + ?Sized,
{
// ... snip
}

  • Listener object can have a definition like the following
pub struct Listener<F>

{
    exec_body : fn(Chat) -> F,

}
  • When BotInstance is initialized with listeners, it should pass exec bodies in the following form
    let bot = BotInstance {

        module : Listener {

            exec_body : testfn2::<dyn std::future::Future<Output = ()>>,

        },

        chat : Chat {

            client : String::from("chat functions"),

        }

    };
  • When calling the Listener exec_body, pass the Chat object
(bot.module.exec_body)(bot.chat).await;  
After testing in a sandbox project, I believe I'm able to get a working implementation that stores an `async` function into a struct So it looks like a few things that I can keep in mind : - To avoid infinite or cyclical types, I should have `Listener` or `ModuleManager` interact with `Chat` commands through some intermediary like `BotInstance`, but I should not pass `BotInstance` to any of these, as `BotInstance` contains these structs - Define the custom function to be `async`, and has a signature similar to the following ```rust async fn testfn2<F>(chat:Chat) where     F : std::future::Future + ?Sized, { // ... snip } ``` - `Listener` object can have a definition like the following ```rust pub struct Listener<F> {     exec_body : fn(Chat) -> F, } ``` - When `BotInstance` is initialized with listeners, it should pass exec bodies in the following form ```rust     let bot = BotInstance {         module : Listener {             exec_body : testfn2::<dyn std::future::Future<Output = ()>>,         },         chat : Chat {             client : String::from("chat functions"),         }     }; ``` - When calling the `Listener` exec_body, pass the `Chat` object ```rust (bot.module.exec_body)(bot.chat).await; ```
Author
Owner

After discussing with tori, finally after so much random troubleshooting on my part, tori was able to identify a solution that allowed an async exec_body within a struct to be executed , and not have other issues - namely, not have issues storing in a HashMap of other Actions of the same type

The following is a very small demonstration of working code , that I've encapsulated a bit in the hopes that a module developer can create an execution body with ease for botactions they create

use tokio;

use std::collections::HashMap;


use bot_actions::actions_util;

#[tokio::main]
async fn main() {
    
    
    let mut mods = HashMap::new();
    
    mods.insert("1",bot_actions::Listener {
        exec_body : actions_util::fnenvelope(tester),
    });
    


    mods.insert("2",bot_actions::Listener {
        exec_body : actions_util::fnenvelope(tester2),
    });


    
    for (k,v) in mods {
        println!("{k}");
        (v.exec_body)().await;
    }
    
    
}




async fn tester() {
    println!("tester");
}

async fn tester2() {
    println!("tester2");
}



mod bot_actions {

    use std::boxed::Box;
    use std::pin::Pin;
    use std::future::Future;

    type ExecBody<F> = Box<dyn FnOnce() -> Pin<Box<dyn Future<Output = F>>>>;
    
    pub struct Listener<F> {
        pub exec_body : ExecBody<F>,
    }
    
    pub mod actions_util {
    
        use std::future::Future;
        use std::boxed::Box;
        use std::pin::Pin;
        
        type ExecBody<F> = Box<dyn FnOnce() -> Pin<Box<dyn Future<Output = F>>>>;
        
        pub fn fnenvelope<F, T>(f: fn() -> T) -> ExecBody<F>
        where
            T: Future<Output = F> + 'static,
        {
            Box::new(move || Box::pin(f()))
        }
    }

    
}




The following are next :

  • implement above working sample code into bot core
After discussing with tori, finally after so much random troubleshooting on my part, tori was able to identify a solution that allowed an async exec_body within a struct to be executed , and not have other issues - namely, not have issues storing in a HashMap of other Actions of the same type The following is a very small demonstration of working code , that I've encapsulated a bit in the hopes that a module developer can create an execution body with ease for botactions they create ```rust use tokio; use std::collections::HashMap; use bot_actions::actions_util; #[tokio::main] async fn main() { let mut mods = HashMap::new(); mods.insert("1",bot_actions::Listener { exec_body : actions_util::fnenvelope(tester), }); mods.insert("2",bot_actions::Listener { exec_body : actions_util::fnenvelope(tester2), }); for (k,v) in mods { println!("{k}"); (v.exec_body)().await; } } async fn tester() { println!("tester"); } async fn tester2() { println!("tester2"); } mod bot_actions { use std::boxed::Box; use std::pin::Pin; use std::future::Future; type ExecBody<F> = Box<dyn FnOnce() -> Pin<Box<dyn Future<Output = F>>>>; pub struct Listener<F> { pub exec_body : ExecBody<F>, } pub mod actions_util { use std::future::Future; use std::boxed::Box; use std::pin::Pin; type ExecBody<F> = Box<dyn FnOnce() -> Pin<Box<dyn Future<Output = F>>>>; pub fn fnenvelope<F, T>(f: fn() -> T) -> ExecBody<F> where T: Future<Output = F> + 'static, { Box::new(move || Box::pin(f())) } } } ``` ---- The following are next : - [ ] implement above working sample code into bot core
Author
Owner

I was initially running into an issue where defining an Async fn with parameters were throwing compile errors, but the following is working example with an argument/parameter

use tokio;

use std::collections::HashMap;

use std::boxed::Box;
use std::pin::Pin;
use std::future::Future;


#[tokio::main]
async fn main() {
    
    
    let mut mods = HashMap::new();
    
    mods.insert("1",Listener {
        exec_body : force_boxed(tester2),
    });
    

    
    mods.insert("2",Listener {
        exec_body : force_boxed(tester),
    });
    
    

    
    for (k,v) in mods {
        println!("{k}");
        (v.exec_body)(Chat{}).await;
    }
    
    
}


struct Listener<F> {
    // pub exec_body : fn(Chat) -> F,
    pub exec_body : ExecBody<F>,
 
}


async fn tester(_a:Chat) {
    println!("tester");
}

async fn tester2(_a:Chat) {
    println!("tester2");
}

struct Chat {}

type ExecBody<F> = Box<dyn Fn(Chat) -> Pin<Box<dyn Future<Output = F>>>>;

fn force_boxed<F, T>(f: fn(Chat) -> T) -> ExecBody<F>
where
    T: Future<Output = F> + 'static,
{
    Box::new(move |a| Box::pin(f(a)))
}

I was initially running into an issue where defining an Async fn with parameters were throwing compile errors, but the following is working example with an argument/parameter ```rust use tokio; use std::collections::HashMap; use std::boxed::Box; use std::pin::Pin; use std::future::Future; #[tokio::main] async fn main() { let mut mods = HashMap::new(); mods.insert("1",Listener { exec_body : force_boxed(tester2), }); mods.insert("2",Listener { exec_body : force_boxed(tester), }); for (k,v) in mods { println!("{k}"); (v.exec_body)(Chat{}).await; } } struct Listener<F> { // pub exec_body : fn(Chat) -> F, pub exec_body : ExecBody<F>, } async fn tester(_a:Chat) { println!("tester"); } async fn tester2(_a:Chat) { println!("tester2"); } struct Chat {} type ExecBody<F> = Box<dyn Fn(Chat) -> Pin<Box<dyn Future<Output = F>>>>; fn force_boxed<F, T>(f: fn(Chat) -> T) -> ExecBody<F> where T: Future<Output = F> + 'static, { Box::new(move |a| Box::pin(f(a))) } ```
Author
Owner

As of 3f6e3710ef in Dev branch, I can now add working execution bodies for both Listener and BotCommand

The resolution took the ideas from this issue, and melded it with my Rust_Notes Obsidian Vault for 20240105_DESIGN_Working_Custom_Exec_Body - on the rust note repo as https://git.flake.sh/modulatingforce/Rust_Notes/src/branch/main/forcebot_rs%20Notes/Issues/20240105_DESIGN_Working_Custom_Exec_Body/20240105_DESIGN_Working_Custom_Exec_Body.md

I'll keep this in monitoring for now for a few days before I move this in the project to Review for Completion

As of 3f6e3710ef953f70124a7e3a5be07120feac1e03 in Dev branch, I can now add working execution bodies for both `Listener` and `BotCommand` The resolution took the ideas from this issue, and melded it with my Rust_Notes Obsidian Vault for 20240105_DESIGN_Working_Custom_Exec_Body - on the rust note repo as https://git.flake.sh/modulatingforce/Rust_Notes/src/branch/main/forcebot_rs%20Notes/Issues/20240105_DESIGN_Working_Custom_Exec_Body/20240105_DESIGN_Working_Custom_Exec_Body.md I'll keep this in monitoring for now for a few days before I move this in the project to Review for Completion
modulatingforce self-assigned this 2024-03-02 21:00:35 -05:00
Author
Owner

No longer an issue

Current working implementation uses Arc<RwLock<BotInstance>> to pass to custom functions

No longer an issue Current working implementation uses `Arc<RwLock<BotInstance>>` to pass to custom functions
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: modulatingforce/forcebot_rs#7
No description provided.