use once_cell::sync::Lazy;
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::{PoisonError, RwLock};
use std::time::SystemTime;
use super::errors::{InstanceError, InstanceResult};
pub(crate) struct GameInstance {
pub pid: u32,
pub instance_name: String,
#[allow(dead_code)]
pub version: String,
#[allow(dead_code)]
pub username: String,
#[allow(dead_code)]
pub game_dir: PathBuf,
#[allow(dead_code)]
pub started_at: SystemTime,
}
pub(crate) struct InstanceManager {
instances: RwLock<HashMap<u32, GameInstance>>,
}
pub(crate) static INSTANCE_MANAGER: Lazy<InstanceManager> = Lazy::new(InstanceManager::new);
impl InstanceManager {
pub fn new() -> Self {
Self {
instances: RwLock::new(HashMap::new()),
}
}
pub fn get_pid(&self, instance_name: &str) -> Option<u32> {
let instances = self
.instances
.read()
.unwrap_or_else(PoisonError::into_inner);
instances
.values()
.find(|inst| inst.instance_name == instance_name)
.map(|inst| inst.pid)
}
pub fn get_pids(&self, instance_name: &str) -> Vec<u32> {
let instances = self
.instances
.read()
.unwrap_or_else(PoisonError::into_inner);
instances
.values()
.filter(|inst| inst.instance_name == instance_name)
.map(|inst| inst.pid)
.collect()
}
pub fn is_alive(&self, pid: u32) -> bool {
let instances = self
.instances
.read()
.unwrap_or_else(PoisonError::into_inner);
instances.contains_key(&pid)
}
pub async fn register_instance(&self, instance: GameInstance) -> InstanceResult<()> {
let mut instances = self
.instances
.write()
.unwrap_or_else(PoisonError::into_inner);
if let Some(existing) = instances.get(&instance.pid) {
return Err(InstanceError::DuplicatePid {
pid: instance.pid,
existing_instance: existing.instance_name.clone(),
});
}
instances.insert(instance.pid, instance);
Ok(())
}
pub async fn unregister_instance(&self, pid: u32) {
let mut instances = self
.instances
.write()
.unwrap_or_else(PoisonError::into_inner);
instances.remove(&pid);
}
pub async fn close_instance(&self, pid: u32) -> InstanceResult<()> {
let mut instances = self
.instances
.write()
.unwrap_or_else(PoisonError::into_inner);
instances
.remove(&pid)
.ok_or(InstanceError::NotFound { pid })?;
drop(instances);
#[cfg(target_os = "windows")]
{
use std::process::Command;
let output = Command::new("taskkill")
.args(&["/PID", &pid.to_string(), "/F"])
.output()?;
if !output.status.success() {
lighty_core::trace_warn!(pid = pid, "Failed to kill process");
} else {
lighty_core::trace_info!(pid = pid, "Instance killed");
}
}
#[cfg(not(target_os = "windows"))]
{
use nix::sys::signal::{kill, Signal};
use nix::unistd::Pid;
match kill(Pid::from_raw(pid as i32), Signal::SIGTERM) {
Ok(_) => {
lighty_core::trace_info!(pid = pid, "Instance killed");
}
Err(e) => {
lighty_core::trace_warn!(pid = pid, error = %e, "Failed to kill process");
return Err(InstanceError::Io(std::io::Error::new(
std::io::ErrorKind::Other,
format!("Failed to kill process: {}", e),
)));
}
}
}
Ok(())
}
}