Updates
This commit is contained in:
		@@ -27,7 +27,8 @@
 | 
			
		||||
//!          └───────────────┘
 | 
			
		||||
//! ```
 | 
			
		||||
 | 
			
		||||
use hero_job::Job;
 | 
			
		||||
use hero_job::{Job, ScriptType};
 | 
			
		||||
use hero_job::keys;
 | 
			
		||||
use log::{debug, error, info};
 | 
			
		||||
use redis::AsyncCommands;
 | 
			
		||||
 | 
			
		||||
@@ -36,7 +37,7 @@ use std::time::Duration;
 | 
			
		||||
use tokio::sync::mpsc;
 | 
			
		||||
use tokio::task::JoinHandle;
 | 
			
		||||
 | 
			
		||||
use crate::{initialize_redis_connection, NAMESPACE_PREFIX, BLPOP_TIMEOUT_SECONDS};
 | 
			
		||||
use crate::{initialize_redis_connection, BLPOP_TIMEOUT_SECONDS};
 | 
			
		||||
 | 
			
		||||
/// Configuration for actor instances
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
@@ -123,11 +124,14 @@ pub trait Actor: Send + Sync + 'static {
 | 
			
		||||
        tokio::spawn(async move {
 | 
			
		||||
            let actor_id = self.actor_id();
 | 
			
		||||
            let redis_url = self.redis_url();
 | 
			
		||||
            let queue_key = format!("{}{}", NAMESPACE_PREFIX, actor_id);
 | 
			
		||||
            // Canonical work queue based on script type (instance/group selection can be added later)
 | 
			
		||||
            let script_type = derive_script_type_from_actor_id(actor_id);
 | 
			
		||||
            let queue_key = keys::work_type(&script_type);
 | 
			
		||||
            info!(
 | 
			
		||||
                "{} Actor '{}' starting. Connecting to Redis at {}. Listening on queue: {}",
 | 
			
		||||
                "{} Actor '{}' starting. Type {:?}. Connecting to Redis at {}. Listening on queue: {}",
 | 
			
		||||
                self.actor_type(),
 | 
			
		||||
                actor_id,
 | 
			
		||||
                script_type,
 | 
			
		||||
                redis_url,
 | 
			
		||||
                queue_key
 | 
			
		||||
            );
 | 
			
		||||
@@ -254,78 +258,18 @@ pub fn spawn_actor<W: Actor>(
 | 
			
		||||
    actor.spawn(shutdown_rx)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use crate::engine::create_heromodels_engine;
 | 
			
		||||
 | 
			
		||||
    // Mock actor for testing
 | 
			
		||||
    struct MockActor;
 | 
			
		||||
 | 
			
		||||
    #[async_trait::async_trait]
 | 
			
		||||
    impl Actor for MockActor {
 | 
			
		||||
        async fn process_job(
 | 
			
		||||
            &self,
 | 
			
		||||
            _job: Job,
 | 
			
		||||
            _redis_conn: &mut redis::aio::MultiplexedConnection,
 | 
			
		||||
        ) {
 | 
			
		||||
            // Mock implementation - do nothing
 | 
			
		||||
            // Engine would be owned by the actor implementation as a field
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fn actor_type(&self) -> &'static str {
 | 
			
		||||
            "Mock"
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        fn actor_id(&self) -> &str {
 | 
			
		||||
            "mock_actor"
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        fn redis_url(&self) -> &str {
 | 
			
		||||
            "redis://localhost:6379"
 | 
			
		||||
        }
 | 
			
		||||
fn derive_script_type_from_actor_id(actor_id: &str) -> ScriptType {
 | 
			
		||||
    let lower = actor_id.to_lowercase();
 | 
			
		||||
    if lower.contains("sal") {
 | 
			
		||||
        ScriptType::SAL
 | 
			
		||||
    } else if lower.contains("osis") {
 | 
			
		||||
        ScriptType::OSIS
 | 
			
		||||
    } else if lower.contains("python") {
 | 
			
		||||
        ScriptType::Python
 | 
			
		||||
    } else if lower.contains("v") {
 | 
			
		||||
        ScriptType::V
 | 
			
		||||
    } else {
 | 
			
		||||
        // Default to OSIS when uncertain
 | 
			
		||||
        ScriptType::OSIS
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[tokio::test]
 | 
			
		||||
    async fn test_actor_config_creation() {
 | 
			
		||||
        let config = ActorConfig::new(
 | 
			
		||||
            "test_actor".to_string(),
 | 
			
		||||
            "/tmp".to_string(),
 | 
			
		||||
            "redis://localhost:6379".to_string(),
 | 
			
		||||
            false,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        assert_eq!(config.actor_id, "test_actor");
 | 
			
		||||
        assert_eq!(config.db_path, "/tmp");
 | 
			
		||||
        assert_eq!(config.redis_url, "redis://localhost:6379");
 | 
			
		||||
        assert!(!config.preserve_tasks);
 | 
			
		||||
        assert!(config.default_timeout.is_none());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[tokio::test]
 | 
			
		||||
    async fn test_actor_config_with_timeout() {
 | 
			
		||||
        let timeout = Duration::from_secs(300);
 | 
			
		||||
        let config = ActorConfig::new(
 | 
			
		||||
            "test_actor".to_string(),
 | 
			
		||||
            "/tmp".to_string(),
 | 
			
		||||
            "redis://localhost:6379".to_string(),
 | 
			
		||||
            false,
 | 
			
		||||
        ).with_default_timeout(timeout);
 | 
			
		||||
 | 
			
		||||
        assert_eq!(config.default_timeout, Some(timeout));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[tokio::test]
 | 
			
		||||
    async fn test_spawn_actor_function() {
 | 
			
		||||
        let (_shutdown_tx, shutdown_rx) = mpsc::channel(1);
 | 
			
		||||
        let actor = Arc::new(MockActor);
 | 
			
		||||
 | 
			
		||||
        let handle = spawn_actor(actor, shutdown_rx);
 | 
			
		||||
        
 | 
			
		||||
        // The actor should be created successfully
 | 
			
		||||
        assert!(!handle.is_finished());
 | 
			
		||||
        
 | 
			
		||||
        // Abort the actor for cleanup
 | 
			
		||||
        handle.abort();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
use hero_job::{Job, JobStatus};
 | 
			
		||||
use hero_job::{Job, JobStatus, ScriptType};
 | 
			
		||||
use hero_job::keys;
 | 
			
		||||
use log::{debug, error, info};
 | 
			
		||||
use redis::AsyncCommands;
 | 
			
		||||
use rhai::{Dynamic, Engine};
 | 
			
		||||
@@ -217,10 +218,11 @@ pub fn spawn_rhai_actor(
 | 
			
		||||
    preserve_tasks: bool,
 | 
			
		||||
) -> JoinHandle<Result<(), Box<dyn std::error::Error + Send + Sync>>> {
 | 
			
		||||
    tokio::spawn(async move {
 | 
			
		||||
        let queue_key = format!("{}{}", NAMESPACE_PREFIX, actor_id);
 | 
			
		||||
        let script_type = derive_script_type_from_actor_id(&actor_id);
 | 
			
		||||
        let queue_key = keys::work_type(&script_type);
 | 
			
		||||
        info!(
 | 
			
		||||
            "Rhai Actor for Actor ID '{}' starting. Connecting to Redis at {}. Listening on queue: {}. Waiting for tasks or shutdown signal.",
 | 
			
		||||
            actor_id, redis_url, queue_key
 | 
			
		||||
            "Rhai Actor '{}' starting. Type {:?}. Connecting to Redis at {}. Listening on queue: {}. Waiting for tasks or shutdown signal.",
 | 
			
		||||
            actor_id, script_type, redis_url, queue_key
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        let mut redis_conn = initialize_redis_connection(&actor_id, &redis_url).await?;
 | 
			
		||||
@@ -259,6 +261,23 @@ pub fn spawn_rhai_actor(
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Helper to derive script type from actor_id for canonical queue selection
 | 
			
		||||
fn derive_script_type_from_actor_id(actor_id: &str) -> ScriptType {
 | 
			
		||||
    let lower = actor_id.to_lowercase();
 | 
			
		||||
    if lower.contains("sal") {
 | 
			
		||||
        ScriptType::SAL
 | 
			
		||||
    } else if lower.contains("osis") {
 | 
			
		||||
        ScriptType::OSIS
 | 
			
		||||
    } else if lower.contains("python") {
 | 
			
		||||
        ScriptType::Python
 | 
			
		||||
    } else if lower == "v" || lower.contains(":v") || lower.contains(" v") {
 | 
			
		||||
        ScriptType::V
 | 
			
		||||
    } else {
 | 
			
		||||
        // Default to OSIS when uncertain
 | 
			
		||||
        ScriptType::OSIS
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Re-export the main trait-based interface for convenience
 | 
			
		||||
pub use actor_trait::{Actor, ActorConfig, spawn_actor};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@ use crossterm::{
 | 
			
		||||
    execute,
 | 
			
		||||
};
 | 
			
		||||
use hero_job::{Job, JobStatus, ScriptType};
 | 
			
		||||
use hero_job::keys;
 | 
			
		||||
 | 
			
		||||
use ratatui::{
 | 
			
		||||
    backend::{Backend, CrosstermBackend},
 | 
			
		||||
@@ -457,9 +458,9 @@ impl App {
 | 
			
		||||
        let mut conn = self.redis_client.get_multiplexed_async_connection().await?;
 | 
			
		||||
        job.store_in_redis(&mut conn).await?;
 | 
			
		||||
        
 | 
			
		||||
        // Add to work queue
 | 
			
		||||
        let queue_name = format!("hero:job:actor_queue:{}", self.actor_id.to_lowercase());
 | 
			
		||||
        let _: () = conn.lpush(&queue_name, &job_id).await?;
 | 
			
		||||
        // Add to work queue (canonical type queue)
 | 
			
		||||
        let queue_name = keys::work_type(&self.job_form.script_type);
 | 
			
		||||
        let _: () = conn.lpush(&queue_name, &job.id).await?;
 | 
			
		||||
        
 | 
			
		||||
        self.status_message = Some(format!("Job {} dispatched successfully", job_id));
 | 
			
		||||
        
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user