rstango 0.3.0

Implementation of the Tango distributed control system
Documentation
//! Implementation of the classes of a server.

use std::sync::{Arc, Mutex, Weak};
use std::collections::HashMap;
use std::fmt::{self, Debug};
use std::future::Future;

use crate::dev::{All, Named, ToNames, ToNamesNotAll};
use crate::database::{GetPropSpec, SetPropSpec};
use crate::error::{Error, ErrorKind, Result};
use crate::name::{ArcName, Name};
use crate::value::Spectrum;

use super::{reserved_name, Model, Ref, RESERVED_ATTRS};
use super::dev::{AnyDev, Dev};
use super::server::Server;
use super::def::ClassDef;


/// A class of any model, which is how the server holds its classes.
pub trait AnyClass: Debug + Send + Sync {

    /// The name of that class.
    fn name(&self) -> &str;

    /// The rank of that class among the classes of its server, which is the order they
    /// were made in and the order their devices are initialized in, see [`Server::sync`].
    fn index(&self) -> usize;

    /// Add a device of that class to the server, see [`Class::add_dev`].
    fn add_dev(&self, name: &str) -> Result<Arc<dyn AnyDev>>;

}

pub struct Class<M: Model> {
    /// The name of that class, as its definition gave it, shared with the key it is
    /// registered under in the server.
    name: Arc<str>,
    /// See [`AnyClass::index`].
    index: usize,
    /// Self reference, for what a class hands its devices of itself.
    this: Weak<Self>,
    server: Arc<Server>,
    def: Arc<ClassDef<M>>,
    /// The properties the database holds for the attributes of this class, by attribute
    /// name, which every device of the class is configured from, see [`Self::attr_props`].
    attr_props: Mutex<HashMap<ArcName, Arc<[Named<Spectrum<str>>]>>>,
}

impl<M: Model> Class<M> {

    /// A new class of that server, from its definition, at that rank among the classes of
    /// the server, see [`AnyClass::index`].
    pub(super) fn new(server: Arc<Server>, def: Arc<ClassDef<M>>, index: usize) -> Arc<Self> {
        Arc::new_cyclic(|this| Self {
            name: Arc::from(&*def.name),
            index,
            this: this.clone(),
            server,
            def,
            attr_props: Mutex::new(HashMap::new()),
        })
    }

    #[inline]
    pub fn name(&self) -> &str {
        &self.name
    }

    #[inline]
    pub fn server(&self) -> &Server {
        &self.server
    }

    // The properties of a class are the database's: everything below asks the database 
    // of the server for what it holds of this class, see the very same methods on
    // [`crate::database::DatabaseProxy`]. The blocking forms are for synchronous handlers, 
    // which run on the pool, and must never run in an asynchronous one.

    /// The names of the properties of this class that match the given filter.
    pub async fn list_prop_async(&self, filter: &str) -> Result<Spectrum<str>> {
        self.server().database().list_class_prop_async(&self.name, filter).await
    }

    /// The names of the properties of this class, see [`Self::list_prop_async`].
    pub fn list_prop(&self, filter: &str) -> Result<Spectrum<str>> {
        self.server().database().list_class_prop(&self.name, filter)
    }

    /// The properties of this class, parsed into the shape the given names ask for, see
    /// [`crate::database::DatabaseProxy::get_class_prop`].
    pub fn get_prop_async<Spec, Names: ToNames>(&self, names: Names) -> impl Future<Output = Result<Spec::Output>> + Send
    where
        Spec: GetPropSpec<Names>,
    {
        self.server().database().get_class_prop_async::<Spec, Names>(&self.name, names)
    }

    /// The properties of this class, see [`Self::get_prop_async`].
    pub fn get_prop<Spec, Names: ToNames>(&self, names: Names) -> Result<Spec::Output>
    where
        Spec: GetPropSpec<Names>,
    {
        self.server().database().get_class_prop::<Spec, Names>(&self.name, names)
    }

    /// Write the given properties of this class.
    pub fn set_prop_async<'a, Spec>(&'a self, spec: Spec) -> impl Future<Output = Result<()>> + Send
    where
        Spec: SetPropSpec<'a>,
    {
        self.server().database().set_class_prop_async(&self.name, spec)
    }

    /// Write the given properties of this class, see [`Self::set_prop_async`].
    pub fn set_prop<'a, Spec>(&'a self, spec: Spec) -> Result<()>
    where
        Spec: SetPropSpec<'a>,
    {
        self.server().database().set_class_prop(&self.name, spec)
    }

    /// Delete the given properties of this class.
    pub fn del_prop_async<Names: ToNamesNotAll>(&self, names: Names) -> impl Future<Output = Result<()>> + Send {
        self.server().database().del_class_prop_async(&self.name, names)
    }

    /// Delete the given properties of this class, see [`Self::del_prop_async`].
    pub fn del_prop<Names: ToNamesNotAll>(&self, names: Names) -> Result<()> {
        self.server().database().del_class_prop(&self.name, names)
    }

    /// The properties the database holds for the attributes of this class of those names,
    /// in the same order, an attribute it holds nothing for having none.
    /// 
    /// They are the same for every device of the class, so the database is only asked the
    /// first time a device has an attribute of that name, and never again: what a device
    /// is configured with only changes at the level of the device itself, which is the
    /// one a client changes.
    ///
    /// The State and the Status have none, whatever the database holds: they are the
    /// server's own, which only the device configures, the way cppTango does.
    pub(super) async fn attr_props(&self, names: &[&str]) -> Result<Vec<Arc<[Named<Spectrum<str>>]>>> {

        // Only refetch the missing attribute names.
        let missing = {
            let attr_props = self.attr_props.lock().unwrap();
            names.iter()
                .copied()
                .filter(|name| !attr_props.contains_key(Name::new(name)))
                .filter(|name| reserved_name(name, RESERVED_ATTRS).is_none())
                .collect::<Vec<_>>()
        };

        if !missing.is_empty() {
            
            let fetched = self.server()
                .database()
                .get_class_attr_prop_async::<Spectrum<str>, _, _>(&self.name, missing.as_slice(), All).await?;

            let mut attr_props = self.attr_props.lock().unwrap();
            for (name, props) in missing.into_iter().zip(fetched) {
                attr_props.insert(ArcName::from(Arc::<str>::from(name)), Arc::from(props?));
            }

        }

        let attr_props = self.attr_props.lock().unwrap();
        Ok(names
            .iter()
            .map(|name| attr_props.get(Name::new(name))
                .map_or_else(|| Arc::from([]), Arc::clone))
            .collect())

    }

    #[inline]
    pub fn def(&self) -> &ClassDef<M> {
        &self.def
    }

    /// Add a device of that class to the server, whose name has to be unique server-wide.
    /// 
    /// The device is only registered: it is initialized, served and exported by the next
    /// [`Server::sync`].
    pub fn add_dev(&self, name: &str) -> Result<Ref<'_, Dev<M>>> {
        self.new_dev(name).map(Ref::new)
    }

    /// See [`Self::add_dev`].
    fn new_dev(&self, name: &str) -> Result<Arc<Dev<M>>> {

        let mut server_devs = self.server.devs.lock().unwrap();
        if server_devs.contains_key(Name::new(name)) {
            return Err(Error::new(ErrorKind::DevAlreadyDefined)
                .with_append_description(format_args!("Device {name:?}")));
        }

        let name = Arc::<str>::from(name);
        // A class is only ever reached through a reference to it, so it is alive.
        let dev = Dev::new(self.this.upgrade().unwrap(), Arc::clone(&name));
        server_devs.insert(ArcName::from(name), Arc::clone(&dev) as Arc<dyn AnyDev>);
        drop(server_devs);

        self.server.pending.lock().unwrap().added.push(Arc::clone(&dev) as Arc<dyn AnyDev>);
        Ok(dev)

    }

}

impl<M: Model> AnyClass for Class<M> {

    #[inline]
    fn name(&self) -> &str {
        &self.name
    }

    #[inline]
    fn index(&self) -> usize {
        self.index
    }

    fn add_dev(&self, name: &str) -> Result<Arc<dyn AnyDev>> {
        self.new_dev(name).map(|dev| dev as Arc<dyn AnyDev>)
    }

}

impl<M: Model> Debug for Class<M> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("Class").field("name", &self.name).finish_non_exhaustive()
    }
}