#![allow(clippy::non_std_lazy_statics)]
use crate::runtime::InterpreterError;
use std::collections::{HashMap, VecDeque};
use std::sync::{Arc, Mutex, RwLock};
#[derive(Debug, Clone)]
pub struct ActorMessage {
pub message_type: String,
pub data: Vec<String>, }
#[derive(Debug)]
pub struct ActorMailbox {
messages: VecDeque<ActorMessage>,
capacity: usize,
}
impl ActorMailbox {
pub fn new(capacity: usize) -> Self {
Self {
messages: VecDeque::with_capacity(capacity),
capacity,
}
}
pub fn enqueue(&mut self, message: ActorMessage) -> Result<(), String> {
if self.messages.len() >= self.capacity {
return Err("Mailbox full".to_string());
}
self.messages.push_back(message);
Ok(())
}
pub fn dequeue(&mut self) -> Option<ActorMessage> {
self.messages.pop_front()
}
pub fn is_empty(&self) -> bool {
self.messages.is_empty()
}
pub fn len(&self) -> usize {
self.messages.len()
}
}
#[derive(Debug)]
pub struct ActorInstance {
pub actor_type: String,
pub state: HashMap<String, ActorFieldValue>,
pub mailbox: ActorMailbox,
pub receive_handlers: HashMap<String, String>, }
#[derive(Debug, Clone, PartialEq)]
pub enum ActorFieldValue {
Integer(i64),
Float(f64),
String(String),
Bool(bool),
Nil,
}
impl ActorFieldValue {
pub fn from_value(value: &crate::runtime::Value) -> Self {
use crate::runtime::Value;
match value {
Value::Integer(i) => ActorFieldValue::Integer(*i),
Value::Float(f) => ActorFieldValue::Float(*f),
Value::String(s) => ActorFieldValue::String(s.to_string()),
Value::Bool(b) => ActorFieldValue::Bool(*b),
_ => ActorFieldValue::Nil,
}
}
pub fn to_value(&self) -> crate::runtime::Value {
use crate::runtime::Value;
match self {
ActorFieldValue::Integer(i) => Value::Integer(*i),
ActorFieldValue::Float(f) => Value::Float(*f),
ActorFieldValue::String(s) => Value::from_string(s.clone()),
ActorFieldValue::Bool(b) => Value::Bool(*b),
ActorFieldValue::Nil => Value::Nil,
}
}
}
impl ActorInstance {
pub fn new(actor_type: String, initial_state: HashMap<String, ActorFieldValue>) -> Self {
Self {
actor_type,
state: initial_state,
mailbox: ActorMailbox::new(1000), receive_handlers: HashMap::new(),
}
}
pub fn process_message(
&mut self,
message: &ActorMessage,
) -> Result<Option<ActorFieldValue>, InterpreterError> {
if let Some(_handler) = self.receive_handlers.get(&message.message_type) {
if message.message_type == "Increment" {
if let Some(ActorFieldValue::Integer(count)) = self.state.get("count") {
let new_count = count + 1;
self.state
.insert("count".to_string(), ActorFieldValue::Integer(new_count));
return Ok(Some(ActorFieldValue::Integer(new_count)));
}
}
Ok(Some(ActorFieldValue::Nil))
} else {
Err(InterpreterError::RuntimeError(format!(
"No handler for message type: {}",
message.message_type
)))
}
}
pub fn send(&mut self, message: ActorMessage) -> Result<(), InterpreterError> {
self.mailbox
.enqueue(message)
.map_err(InterpreterError::RuntimeError)
}
pub fn process_mailbox(&mut self) -> Result<Vec<ActorFieldValue>, InterpreterError> {
let mut results = Vec::new();
while let Some(message) = self.mailbox.dequeue() {
if let Some(result) = self.process_message(&message)? {
results.push(result);
}
}
Ok(results)
}
}
pub struct ActorRuntime {
actors: Arc<RwLock<HashMap<String, Arc<Mutex<ActorInstance>>>>>,
next_actor_id: Arc<Mutex<u64>>,
}
impl Default for ActorRuntime {
fn default() -> Self {
Self::new()
}
}
impl ActorRuntime {
pub fn new() -> Self {
Self {
actors: Arc::new(RwLock::new(HashMap::new())),
next_actor_id: Arc::new(Mutex::new(0)),
}
}
pub fn generate_actor_id(&self) -> String {
let mut id = self
.next_actor_id
.lock()
.expect("mutex should not be poisoned");
*id += 1;
format!("actor_{id}")
}
pub fn spawn_actor(
&self,
actor_type: String,
initial_state: HashMap<String, ActorFieldValue>,
receive_handlers: HashMap<String, String>,
) -> Result<String, InterpreterError> {
let actor_id = self.generate_actor_id();
let mut instance = ActorInstance::new(actor_type, initial_state);
instance.receive_handlers = receive_handlers;
let mut actors = self.actors.write().expect("rwlock should not be poisoned");
actors.insert(actor_id.clone(), Arc::new(Mutex::new(instance)));
Ok(actor_id)
}
pub fn send_message(
&self,
actor_id: &str,
message: ActorMessage,
) -> Result<(), InterpreterError> {
let actors = self.actors.read().expect("rwlock should not be poisoned");
if let Some(actor) = actors.get(actor_id) {
let mut instance = actor.lock().expect("mutex should not be poisoned");
instance.send(message)?;
instance.process_mailbox()?;
Ok(())
} else {
Err(InterpreterError::RuntimeError(format!(
"Actor not found: {actor_id}"
)))
}
}
pub fn get_actor_state(
&self,
actor_id: &str,
) -> Result<HashMap<String, ActorFieldValue>, InterpreterError> {
let actors = self.actors.read().expect("rwlock should not be poisoned");
if let Some(actor) = actors.get(actor_id) {
let instance = actor.lock().expect("mutex should not be poisoned");
Ok(instance.state.clone())
} else {
Err(InterpreterError::RuntimeError(format!(
"Actor not found: {actor_id}"
)))
}
}
pub fn get_actor_field(
&self,
actor_id: &str,
field_name: &str,
) -> Result<ActorFieldValue, InterpreterError> {
let actors = self.actors.read().expect("rwlock should not be poisoned");
if let Some(actor) = actors.get(actor_id) {
let instance = actor.lock().expect("mutex should not be poisoned");
Ok(instance
.state
.get(field_name)
.cloned()
.unwrap_or(ActorFieldValue::Nil))
} else {
Err(InterpreterError::RuntimeError(format!(
"Actor not found: {actor_id}"
)))
}
}
}
lazy_static::lazy_static! {
pub static ref ACTOR_RUNTIME: ActorRuntime = ActorRuntime::new();
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mailbox_operations() {
let mut mailbox = ActorMailbox::new(3);
let msg1 = ActorMessage {
message_type: "Test".to_string(),
data: vec![],
};
assert!(mailbox.enqueue(msg1).is_ok());
assert_eq!(mailbox.len(), 1);
let dequeued = mailbox.dequeue();
assert!(dequeued.is_some());
assert_eq!(mailbox.len(), 0);
}
#[test]
fn test_actor_state_modification() {
let mut state = HashMap::new();
state.insert("count".to_string(), ActorFieldValue::Integer(0));
let mut instance = ActorInstance::new("Counter".to_string(), state);
instance
.receive_handlers
.insert("Increment".to_string(), "increment_handler".to_string());
let msg = ActorMessage {
message_type: "Increment".to_string(),
data: vec![],
};
instance
.send(msg)
.expect("operation should succeed in test");
instance
.process_mailbox()
.expect("operation should succeed in test");
assert_eq!(
instance.state.get("count"),
Some(&ActorFieldValue::Integer(1))
);
}
}
#[cfg(test)]
mod mutation_tests {
use super::*;
#[test]
fn test_actor_mailbox_is_empty_not_stub() {
let mut mailbox = ActorMailbox::new(10);
assert!(mailbox.is_empty(), "Empty mailbox should return true");
mailbox
.enqueue(ActorMessage {
message_type: "test".into(),
data: vec![],
})
.expect("operation should succeed in test");
assert!(!mailbox.is_empty(), "Non-empty mailbox should return false");
}
#[test]
fn test_generate_actor_id_unique() {
let runtime = ActorRuntime::new();
let id1 = runtime.generate_actor_id();
let id2 = runtime.generate_actor_id();
assert_ne!(id1, id2, "IDs should be unique");
assert!(id1.starts_with("actor_"), "ID should have prefix");
}
#[test]
fn test_spawn_actor_returns_unique_id() {
let runtime = ActorRuntime::new();
let id1 = runtime
.spawn_actor("TestActor".into(), HashMap::new(), HashMap::new())
.expect("operation should succeed in test");
let id2 = runtime
.spawn_actor("TestActor".into(), HashMap::new(), HashMap::new())
.expect("operation should succeed in test");
assert_ne!(id1, id2, "spawn_actor should return unique IDs");
assert!(!id1.is_empty(), "ID should not be empty string");
}
#[test]
fn test_actor_field_value_bool_match_arm() {
use crate::runtime::Value;
let bool_value = Value::Bool(true);
let field_value = ActorFieldValue::from_value(&bool_value);
assert_eq!(
field_value,
ActorFieldValue::Bool(true),
"Bool match arm should work"
);
let false_value = Value::Bool(false);
let field_false = ActorFieldValue::from_value(&false_value);
assert_eq!(
field_false,
ActorFieldValue::Bool(false),
"Bool false should work"
);
}
#[test]
fn test_actor_message_creation() {
let msg = ActorMessage {
message_type: "Ping".to_string(),
data: vec!["arg1".to_string(), "arg2".to_string()],
};
assert_eq!(msg.message_type, "Ping");
assert_eq!(msg.data.len(), 2);
}
#[test]
fn test_mailbox_capacity_enforced() {
let mut mailbox = ActorMailbox::new(2);
let msg1 = ActorMessage {
message_type: "Test".to_string(),
data: vec![],
};
let msg2 = ActorMessage {
message_type: "Test".to_string(),
data: vec![],
};
let msg3 = ActorMessage {
message_type: "Test".to_string(),
data: vec![],
};
assert!(mailbox.enqueue(msg1).is_ok());
assert!(mailbox.enqueue(msg2).is_ok());
let result = mailbox.enqueue(msg3);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), "Mailbox full");
}
#[test]
fn test_actor_field_value_integer() {
use crate::runtime::Value;
let int_value = Value::Integer(42);
let field = ActorFieldValue::from_value(&int_value);
assert_eq!(field, ActorFieldValue::Integer(42));
let back = field.to_value();
if let Value::Integer(i) = back {
assert_eq!(i, 42);
} else {
panic!("Expected Integer");
}
}
#[test]
fn test_actor_field_value_float() {
use crate::runtime::Value;
let float_value = Value::Float(3.14);
let field = ActorFieldValue::from_value(&float_value);
assert_eq!(field, ActorFieldValue::Float(3.14));
let back = field.to_value();
if let Value::Float(f) = back {
assert!((f - 3.14).abs() < f64::EPSILON);
} else {
panic!("Expected Float");
}
}
#[test]
fn test_actor_field_value_string() {
use crate::runtime::Value;
let str_value = Value::from_string("hello".to_string());
let field = ActorFieldValue::from_value(&str_value);
assert_eq!(field, ActorFieldValue::String("hello".to_string()));
let back = field.to_value();
if let Value::String(s) = back {
assert_eq!(&*s, "hello");
} else {
panic!("Expected String");
}
}
#[test]
fn test_actor_field_value_nil() {
let field = ActorFieldValue::Nil;
let back = field.to_value();
assert!(matches!(back, crate::runtime::Value::Nil));
}
#[test]
fn test_actor_runtime_default() {
let runtime = ActorRuntime::default();
let id = runtime.generate_actor_id();
assert!(id.starts_with("actor_"));
}
#[test]
fn test_get_actor_state_not_found() {
let runtime = ActorRuntime::new();
let result = runtime.get_actor_state("nonexistent");
assert!(result.is_err());
}
#[test]
fn test_get_actor_field_not_found() {
let runtime = ActorRuntime::new();
let result = runtime.get_actor_field("nonexistent", "field");
assert!(result.is_err());
}
#[test]
fn test_send_message_to_nonexistent_actor() {
let runtime = ActorRuntime::new();
let msg = ActorMessage {
message_type: "Test".to_string(),
data: vec![],
};
let result = runtime.send_message("nonexistent", msg);
assert!(result.is_err());
}
#[test]
fn test_actor_instance_process_message_no_handler() {
let mut instance = ActorInstance::new("Test".to_string(), HashMap::new());
let msg = ActorMessage {
message_type: "Unknown".to_string(),
data: vec![],
};
let result = instance.process_message(&msg);
assert!(result.is_err());
}
#[test]
fn test_mailbox_dequeue_empty() {
let mut mailbox = ActorMailbox::new(10);
assert!(mailbox.dequeue().is_none());
}
}