//! Entry point and runtime bootstrap for the Forge Arma server extension. //! //! Initializes a global async runtime, the Redis connection pool, and registers //! all command groups. Provides status/version commands and maintains a shared //! Arma `Context` for engine interop. //! #![allow(future_incompatible)] // Future-incompatible lint is triggered by arma_rs use arma_rs::{Context, Extension, Group, arma}; use std::sync::{LazyLock, OnceLock, RwLock as StdRwLock}; use tokio::runtime::{Builder, Runtime}; use tokio::sync::RwLock as TokioRwLock; pub mod actor; pub mod adapters; pub mod bank; pub mod cad; pub mod garage; pub mod helpers; pub mod icom; pub mod locker; mod log; pub mod org; pub mod redis; pub mod terrain; pub mod transport; pub mod v_garage; pub mod v_locker; /// Global Arma `Context` captured at initialization and made available to /// commands that need engine interop. Stored inside an async `RwLock` to /// allow mutation by the startup task and later reads. static CONTEXT: LazyLock>> = LazyLock::new(|| TokioRwLock::new(None)); /// Global Redis connection pool, created once and shared by all commands. /// Initialized asynchronously after `init()` returns so the extension starts /// quickly without blocking the main thread. static REDIS_POOL: OnceLock = OnceLock::new(); /// Global multi-threaded Tokio runtime used to execute async operations from /// command handlers and startup tasks. pub(crate) static RUNTIME: LazyLock = LazyLock::new(|| { Builder::new_multi_thread() .enable_all() .build() .expect("Failed to create tokio runtime") }); #[derive(Clone, Copy, PartialEq)] /// Connection state for the Redis pool so SQF can gate behavior on readiness. enum ConnectionState { Initializing, Connected, Failed, } static CONNECTION_STATE: LazyLock> = LazyLock::new(|| StdRwLock::new(ConnectionState::Initializing)); pub(crate) fn enqueue_persistence_task(module: &'static str, job: F) where F: FnOnce() -> Result<(), String> + Send + 'static, { RUNTIME.spawn_blocking(move || { if let Err(error) = job() { crate::log::log( module, "ERROR", &format!("Async persistence failed: {}", error), ); } }); } #[arma] /// Initializes the extension, registers commands/groups, and asynchronously /// creates the Redis connection pool on the global runtime. fn init() -> Extension { let config = redis::config::load(); let ext = Extension::build() .command("version", get_version) .command("status", get_status) .group("redis", redis::group()) .group("actor", actor::group()) .group("bank", bank::group()) .group("cad", cad::group()) .group("garage", garage::group()) .group("icom", icom::group()) .group("locker", locker::group()) .group("org", org::group()) .group("terrain", terrain::group()) .group("transport", transport::group()) .group( "owned", Group::new() .group("garage", v_garage::group()) .group("locker", v_locker::group()), ) .finish(); // Spawn initialization tasks for Redis and ICOM // These run asynchronously and don't block extension startup // Redis initialization will set the global CONTEXT RUNTIME.spawn(async move { redis::initialize(config.redis).await; }); ext } /// Returns current Redis connection state as a string: `initializing`, /// `connected`, or `failed`. Intended for SQF polling before issuing /// operations that require Redis. fn get_status() -> String { let state = *CONNECTION_STATE.read().unwrap(); match state { ConnectionState::Initializing => "initializing".into(), ConnectionState::Connected => "connected".into(), ConnectionState::Failed => "failed".into(), } } /// Returns the extension version string for diagnostics and tooling. pub fn get_version() -> String { format!("forge-server v{}", env!("CARGO_PKG_VERSION")) }