//! Actor management operations for the Arma 3 server extension. //! //! Provides Arma 3 extension commands for player data storage, retrieval, and updates. //! Handles SQF command mapping and parameter validation. use arma_rs::{CallContext, Group}; use forge_repositories::{InMemoryActorHotRepository, RedisActorRepository}; use forge_services::{ActorHotStateService, ActorService}; use std::sync::LazyLock; use crate::adapters::ExtensionRedisClient; use crate::enqueue_persistence_task; use crate::helpers::resolve_uid; use crate::log::log; /// Global actor service instance. /// /// Lazily initialized singleton combining Redis adapter, repository, and service layers. static ACTOR_SERVICE: LazyLock>> = LazyLock::new(|| { let redis_client = ExtensionRedisClient::new(); let repository = RedisActorRepository::new(redis_client); ActorService::new(repository) }); static HOT_ACTOR_SERVICE: LazyLock< ActorHotStateService, InMemoryActorHotRepository>, > = LazyLock::new(|| { let redis_client = ExtensionRedisClient::new(); let repository = RedisActorRepository::new(redis_client); let hot_repository = InMemoryActorHotRepository::new(); ActorHotStateService::new(repository, hot_repository) }); /// Creates the Arma 3 command group for actor operations. /// /// Registers commands: `get`, `exists`, `create`, `update`, `delete`. pub fn group() -> Group { Group::new() .command("get", get_actor) .command("create", create_actor) .command("update", update_actor) .command("exists", actor_exists) .command("delete", delete_actor) .group( "hot", Group::new() .command("init", init_hot_actor) .command("get", get_hot_actor) .command("override", override_hot_actor) .command("save", save_hot_actor) .command("remove", remove_hot_actor), ) } fn serialize_hot_actor(actor: forge_models::Actor) -> String { match serde_json::to_string(&actor) { Ok(json) => json, Err(error) => format!("Error: Failed to serialize hot actor: {}", error), } } pub(crate) fn init_hot_actor(call_context: CallContext, key: String) -> String { let resolved_uid = match resolve_uid(&key, &call_context) { Some(uid) => uid, None => return format!("Error: Failed to resolve UID for key: {}", key), }; match HOT_ACTOR_SERVICE.init_actor(resolved_uid) { Ok(actor) => serialize_hot_actor(actor), Err(error) => format!("Error: {}", error), } } pub(crate) fn get_hot_actor(call_context: CallContext, key: String) -> String { let resolved_uid = match resolve_uid(&key, &call_context) { Some(uid) => uid, None => return format!("Error: Failed to resolve UID for key: {}", key), }; match HOT_ACTOR_SERVICE.get_actor(resolved_uid) { Ok(actor) => serialize_hot_actor(actor), Err(error) => format!("Error: {}", error), } } pub(crate) fn override_hot_actor( call_context: CallContext, key: String, json_data: String, ) -> String { let resolved_uid = match resolve_uid(&key, &call_context) { Some(uid) => uid, None => return format!("Error: Failed to resolve UID for key: {}", key), }; match HOT_ACTOR_SERVICE.override_actor(resolved_uid, json_data) { Ok(actor) => serialize_hot_actor(actor), Err(error) => format!("Error: {}", error), } } pub(crate) fn save_hot_actor(call_context: CallContext, key: String) -> String { let resolved_uid = match resolve_uid(&key, &call_context) { Some(uid) => uid, None => return format!("Error: Failed to resolve UID for key: {}", key), }; match HOT_ACTOR_SERVICE.get_actor(resolved_uid.clone()) { Ok(actor) => { enqueue_persistence_task("actor", move || { HOT_ACTOR_SERVICE.save_actor(resolved_uid).map(|_| ()) }); serialize_hot_actor(actor) } Err(error) => format!("Error: {}", error), } } pub(crate) fn remove_hot_actor(call_context: CallContext, key: String) -> String { let resolved_uid = match resolve_uid(&key, &call_context) { Some(uid) => uid, None => return format!("Error: Failed to resolve UID for key: {}", key), }; match HOT_ACTOR_SERVICE.remove_actor(resolved_uid) { Ok(_) => "OK".to_string(), Err(error) => format!("Error: {}", error), } } /// Retrieves an actor by key/UID. /// /// Resolves the key to a Steam UID and returns the actor as JSON. /// Returns an error message if resolution fails or retrieval fails. pub fn get_actor(call_context: CallContext, key: String) -> String { log("actor", "DEBUG", &format!("Getting actor for key: {}", key)); let resolved_uid = match resolve_uid(&key, &call_context) { Some(uid) => { log("actor", "DEBUG", &format!("Resolved UID: {}", uid)); uid } None => { let error_msg = format!("Error: Failed to resolve UID for key: {}", key); log("actor", "ERROR", &error_msg); return error_msg; } }; match ACTOR_SERVICE.get_actor(resolved_uid.clone()) { Ok(actor) => { log( "actor", "INFO", &format!("Successfully retrieved actor: {}", resolved_uid), ); match serde_json::to_string(&actor) { Ok(json) => { log( "actor", "DEBUG", &format!("Serialized actor to JSON: {}", json), ); json } Err(e) => { let error_msg = format!("Error: Failed to serialize actor: {}", e); log("actor", "ERROR", &error_msg); error_msg } } } Err(e) => { let error_msg = format!("Error: {}", e); log( "actor", "ERROR", &format!("Failed to get actor '{}': {}", resolved_uid, e), ); error_msg } } } /// Creates a new actor with the provided JSON data. /// /// Resolves key to UID, validates JSON data, and persists the new actor. pub fn create_actor(call_context: CallContext, key: String, json_data: String) -> String { log( "actor", "DEBUG", &format!("Creating actor for key: {} with data: {}", key, json_data), ); let resolved_uid = match resolve_uid(&key, &call_context) { Some(uid) => { log( "actor", "DEBUG", &format!("Resolved UID for creation: {}", uid), ); uid } None => { let error_msg = format!("Error: Failed to resolve UID for key: {}", key); log("actor", "ERROR", &error_msg); return error_msg; } }; match ACTOR_SERVICE.create_actor(resolved_uid.clone(), json_data) { Ok(actor) => { log( "actor", "INFO", &format!("Successfully created actor: {}", resolved_uid), ); match serde_json::to_string(&actor) { Ok(json) => { log( "actor", "DEBUG", &format!("Serialized actor to JSON: {}", json), ); json } Err(e) => { let error_msg = format!("Error: Failed to serialize actor: {}", e); log("actor", "ERROR", &error_msg); error_msg } } } Err(e) => { let error_msg = format!("Error: {}", e); log( "actor", "ERROR", &format!("Failed to create actor '{}': {}", resolved_uid, e), ); error_msg } } } /// Updates an existing actor with JSON data. /// /// Resolves key to UID, applies partial updates from JSON, and persists changes. pub fn update_actor(call_context: CallContext, key: String, json_update: String) -> String { log( "actor", "DEBUG", &format!("Updating actor for key: {} with data: {}", key, json_update), ); let resolved_uid = match resolve_uid(&key, &call_context) { Some(uid) => { log( "actor", "DEBUG", &format!("Resolved UID for update: {}", uid), ); uid } None => { let error_msg = format!("Error: Failed to resolve UID for key: {}", key); log("actor", "ERROR", &error_msg); return error_msg; } }; match ACTOR_SERVICE.update_actor(resolved_uid.clone(), json_update) { Ok(actor) => { log( "actor", "INFO", &format!("Successfully updated actor: {}", resolved_uid), ); match serde_json::to_string(&actor) { Ok(json) => { log( "actor", "DEBUG", &format!("Serialized updated actor to JSON: {}", json), ); json } Err(e) => { let error_msg = format!("Error: Failed to serialize actor: {}", e); log("actor", "ERROR", &error_msg); error_msg } } } Err(e) => { let error_msg = format!("Error: {}", e); log( "actor", "ERROR", &format!("Failed to update actor '{}': {}", resolved_uid, e), ); error_msg } } } /// Checks if an actor exists in the database. /// /// Returns "true" if the actor exists, "false" otherwise. pub fn actor_exists(call_context: CallContext, key: String) -> String { log( "actor", "DEBUG", &format!("Checking if actor exists for key: {}", key), ); let resolved_uid = match resolve_uid(&key, &call_context) { Some(uid) => { log( "actor", "DEBUG", &format!("Resolved UID for existence check: {}", uid), ); uid } None => { log( "actor", "WARN", &format!("Failed to resolve UID for key: {}", key), ); return "false".to_string(); } }; match ACTOR_SERVICE.actor_exists(resolved_uid.clone()) { Ok(exists) => { log( "actor", "DEBUG", &format!("Actor '{}' exists: {}", resolved_uid, exists), ); exists.to_string() } Err(e) => { log( "actor", "ERROR", &format!("Failed to check if actor '{}' exists: {}", resolved_uid, e), ); "false".to_string() } } } /// Permanently deletes an actor. /// /// Resolves key to UID and removes the actor and associated data. pub fn delete_actor(call_context: CallContext, key: String) -> String { log( "actor", "DEBUG", &format!("Deleting actor for key: {}", key), ); let resolved_uid = match resolve_uid(&key, &call_context) { Some(uid) => { log( "actor", "DEBUG", &format!("Resolved UID for deletion: {}", uid), ); uid } None => { let error_msg = format!("Error: Failed to resolve UID for key: {}", key); log("actor", "ERROR", &error_msg); return error_msg; } }; match ACTOR_SERVICE.delete_actor(resolved_uid.clone()) { Ok(_) => { log( "actor", "INFO", &format!("Successfully deleted actor: {}", resolved_uid), ); "OK".to_string() } Err(e) => { let error_msg = format!("Error: {}", e); log( "actor", "ERROR", &format!("Failed to delete actor '{}': {}", resolved_uid, e), ); error_msg } } }