titanrt 0.7.0

Typed reactive runtime for real-time systems
Documentation
use std::{collections::HashSet, marker::PhantomData, sync::Arc};

use ahash::AHashMap;
use anyhow::{Result, anyhow};
use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
use tokio::runtime::Runtime;

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct SpecificClient<Spec: Clone> {
    pub id: usize,
    pub ip: Option<String>,
    pub spec: Spec,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ClientConfig<Spec: Clone> {
    pub default: Option<Spec>,
    pub specific: Vec<SpecificClient<Spec>>,
    #[serde(default)]
    pub fail_on_empty: bool,
}

pub trait ClientInitializer<Spec: Clone>
where
    Self: Sized,
{
    fn init(spec: &SpecificClient<Spec>, rt: Option<Arc<Runtime>>) -> Result<Self>;
}

#[derive(Clone)]
pub struct ClientsMap<Client: ClientInitializer<Spec>, Spec: Clone> {
    inner: Arc<RwLock<AHashMap<usize, Arc<Client>>>>,
    init_rt: Option<Arc<Runtime>>,
    _spec: PhantomData<Spec>,
}

impl<Client: ClientInitializer<Config>, Config: Clone> ClientsMap<Client, Config> {
    pub fn new(config: &ClientConfig<Config>, rt: Option<Arc<Runtime>>) -> Result<Self> {
        let map = Self {
            inner: Arc::new(RwLock::new(AHashMap::new())),
            init_rt: rt,
            _spec: PhantomData,
        };

        map.rebuild(config)?;

        Ok(map)
    }

    /// Rebuild the map from a full configuration snapshot.
    pub fn rebuild(&self, config: &ClientConfig<Config>) -> Result<()> {
        let mut id_seen: HashSet<usize> = HashSet::default();
        let mut prepared: Vec<(usize, Arc<Client>)> = Vec::with_capacity(config.specific.len() + 1);

        for spec in config.specific.iter() {
            if !id_seen.insert(spec.id) {
                tracing::warn!(
                    "[CientsPool] ID <{}> of IP <{:?}> addr is not unique",
                    spec.id,
                    spec.ip
                );
                continue;
            }

            let client = self.init_client(spec)?;
            prepared.push((spec.id, client));
        }

        if let Some(spec) = &config.default {
            let default_id = id_seen.len();
            let default_client = SpecificClient {
                id: default_id,
                ip: None,
                spec: spec.clone(),
            };
            let client = self.init_client(&default_client)?;
            prepared.push((default_id, client));
        }

        if config.fail_on_empty && prepared.is_empty() {
            return Err(anyhow!("Clients map is empty"));
        }

        let mut guard = self.inner.write();
        guard.clear();
        for (id, client) in prepared {
            guard.insert(id, client);
        }

        Ok(())
    }

    fn init_client(&self, spec: &SpecificClient<Config>) -> Result<Arc<Client>> {
        Client::init(spec, self.init_rt.clone()).map(Arc::new)
    }

    pub fn get(&self, id: &usize) -> Option<Arc<Client>> {
        self.inner.read().get(id).cloned()
    }

    pub fn len(&self) -> usize {
        self.inner.read().len()
    }

    pub fn is_empty(&self) -> bool {
        self.len() == 0
    }

    pub fn contains(&self, id: &usize) -> bool {
        self.inner.read().contains_key(id)
    }

    pub fn ids(&self) -> Vec<usize> {
        self.inner.read().keys().copied().collect()
    }

    pub fn upsert(&self, client: SpecificClient<Config>) -> Result<Arc<Client>> {
        let id = client.id;
        let client = self.init_client(&client)?;
        let mut guard = self.inner.write();
        guard.insert(id, client.clone());
        Ok(client)
    }

    pub fn remove(&self, id: usize) -> Option<Arc<Client>> {
        self.inner.write().remove(&id)
    }

    pub fn next_vacant_id(&self) -> usize {
        let guard = self.inner.read();
        let mut candidate = guard.len();
        while guard.contains_key(&candidate) {
            candidate += 1;
        }
        candidate
    }

    pub fn sole_entry_id(&self) -> Option<usize> {
        let guard = self.inner.read();
        if guard.len() == 1 {
            guard.keys().next().copied()
        } else {
            None
        }
    }
}