cotis 0.1.0-alpha

Modular Rust UI framework core
Documentation
use oorandom::Rand64;
use std::fmt::{Debug, Formatter};
use std::sync::{Arc, Mutex};

#[derive(Clone)]
/// Owned-or-borrowed reference wrapper used across Cotis APIs.
///
/// This allows values to be passed as either owned storage or borrowed references,
/// while presenting a common `AsRef<T>` interface.
pub enum OwnedOrRef<'a, T: ?Sized> {
    Owned(Box<T>),
    Ref(&'a T),
    AsRef(&'a (dyn AsRef<T> + Send + Sync)),
    AsOwnedRef(Arc<dyn AsRef<T> + Send + Sync + 'a>),
}

impl Clone for OwnedOrRef<'_, str> {
    fn clone(&self) -> Self {
        match self {
            OwnedOrRef::Owned(v) => OwnedOrRef::Owned(v.clone()),
            OwnedOrRef::Ref(v) => OwnedOrRef::Ref(*v),
            OwnedOrRef::AsRef(v) => OwnedOrRef::AsRef(*v),
            OwnedOrRef::AsOwnedRef(v) => OwnedOrRef::AsOwnedRef(v.clone()),
        }
    }
}

impl<'a, T: Send + Sync + 'a> OwnedOrRef<'a, T> {
    /// Creates a clone suitable for sharing, promoting owned values to `Arc`-backed storage.
    ///
    /// If `self` is `Owned`, this method converts `self` into `AsOwnedRef` and returns
    /// another `AsOwnedRef` clone pointing to the same allocation.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use cotis::utils::OwnedOrRef;
    ///
    /// let mut value = OwnedOrRef::Owned(Box::new(String::from("cotis")));
    /// let shared = value.share_clone();
    /// assert_eq!(value.as_ref(), "cotis");
    /// assert_eq!(shared.as_ref(), "cotis");
    /// ```
    pub fn share_clone(&mut self) -> Self {
        match self {
            OwnedOrRef::Owned(_) => {
                // SAFETY: We immediately overwrite `*self` before this function returns
                // or unwinds, so the briefly-invalid state is never observable.
                let owned = unsafe { std::ptr::read(self as *const Self) };
                if let OwnedOrRef::Owned(v) = owned {
                    let arc: Arc<dyn AsRef<T> + Send + Sync> = Arc::new(v);
                    // ptr::write overwrites self WITHOUT dropping the old value
                    // (which is now a dangling Box — we already moved it into arc)
                    unsafe { std::ptr::write(self, OwnedOrRef::AsOwnedRef(arc.clone())) };
                    OwnedOrRef::AsOwnedRef(arc)
                } else {
                    unreachable!()
                }
            }
            OwnedOrRef::Ref(v) => OwnedOrRef::Ref(v),
            OwnedOrRef::AsRef(v) => OwnedOrRef::AsRef(*v),
            OwnedOrRef::AsOwnedRef(v) => OwnedOrRef::AsOwnedRef(v.clone()),
        }
    }
}

impl<T: Debug + ?Sized> Debug for OwnedOrRef<'_, T> {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match &self {
            OwnedOrRef::Owned(t) => {
                write!(f, "OwnedOrRef::Owned({:?})", t)
            }
            OwnedOrRef::Ref(t) => {
                write!(f, "OwnedOrRef::Ref({:?})", t)
            }
            OwnedOrRef::AsRef(t) => {
                write!(f, "OwnedOrRef::AsRef(&'a dyn AsRef<{:?}>)", t.as_ref())
            }
            &OwnedOrRef::AsOwnedRef(t) => {
                write!(
                    f,
                    "OwnedOrRef::AsOwnedRef(Arc<dyn AsRef<{:?}>>)",
                    (**t).as_ref()
                )
            }
        }
    }
}

impl<T: ?Sized> AsRef<T> for OwnedOrRef<'_, T> {
    fn as_ref(&self) -> &T {
        match &self {
            OwnedOrRef::Owned(t) => t,
            OwnedOrRef::Ref(t) => t,
            OwnedOrRef::AsRef(t) => t.as_ref(),
            OwnedOrRef::AsOwnedRef(t) => (**t).as_ref(),
        }
    }
}

#[cfg(feature = "serialization")]
/// Serialization support for utility wrapper types.
pub mod serialization {
    use super::*;
    use serde::de::DeserializeOwned;
    use serde::ser::{Serialize, Serializer};
    use serde::{Deserialize, Deserializer};
    // ── Serialize ────────────────────────────────────────────────────────────────
    // Delegates to the inner value regardless of which variant holds it.
    // Requires T: Serialize + ?Sized (e.g. works for str, [u8], concrete types).

    impl<T: Serialize + ?Sized> Serialize for OwnedOrRef<'_, T> {
        fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
            self.as_ref().serialize(serializer)
        }
    }

    // ── Deserialize ──────────────────────────────────────────────────────────────
    // Always produces OwnedOrRef::Owned, since we can't conjure a lifetime-bound
    // reference from a deserializer. Requires T: Sized so we can box it.

    impl<'de, T> Deserialize<'de> for OwnedOrRef<'_, T>
    where
        T: DeserializeOwned,
    {
        fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
            T::deserialize(deserializer).map(|v| OwnedOrRef::Owned(Box::new(v)))
        }
    }

    impl<'de> Deserialize<'de> for OwnedOrRef<'_, str> {
        fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
            String::deserialize(deserializer).map(|v| OwnedOrRef::AsOwnedRef(Arc::new(v)))
        }
    }
}

/// Mutable owned-or-borrowed wrapper.
pub enum OwnedOrRefMut<'a, T: ?Sized> {
    Owned(Box<T>),
    Ref(&'a mut T),
    AsRef(&'a mut (dyn AsMut<T> + Send + Sync)),
}

impl<T: Debug + ?Sized> Debug for OwnedOrRefMut<'_, T> {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match &self {
            OwnedOrRefMut::Owned(t) => {
                write!(f, "OwnedOrRefMut::Owned({:?})", t)
            }
            OwnedOrRefMut::Ref(t) => {
                write!(f, "OwnedOrRefMut::Ref({:?})", t)
            }
            OwnedOrRefMut::AsRef(_) => {
                write!(f, "OwnedOrRefMut::AsRef(&'a dyn AsRef<..>)")
            }
        }
    }
}

impl<T: ?Sized> AsMut<T> for OwnedOrRefMut<'_, T> {
    fn as_mut(&mut self) -> &mut T {
        match self {
            OwnedOrRefMut::Owned(t) => t,
            OwnedOrRefMut::Ref(t) => t,
            OwnedOrRefMut::AsRef(t) => t.as_mut(),
        }
    }
}

impl<T: ?Sized> OwnedOrRefMut<'_, T> {
    /// Reborrows this mutable wrapper for a shorter lifetime.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use cotis::utils::OwnedOrRefMut;
    ///
    /// let mut value = 10;
    /// let mut wrapper = OwnedOrRefMut::Ref(&mut value);
    /// {
    ///     let mut reborrowed = wrapper.reborrow();
    ///     *reborrowed.as_mut() += 5;
    /// }
    /// assert_eq!(value, 15);
    /// ```
    pub fn reborrow(&'_ mut self) -> OwnedOrRefMut<'_, T> {
        match self {
            OwnedOrRefMut::Owned(t) => OwnedOrRefMut::Ref(&mut *t),
            OwnedOrRefMut::Ref(t) => OwnedOrRefMut::Ref(*t),
            OwnedOrRefMut::AsRef(t) => OwnedOrRefMut::Ref(t.as_mut()),
        }
    }
}

#[derive(Clone, Debug)]
#[cfg_attr(
    feature = "serialization",
    derive(serde::Serialize, serde::Deserialize)
)]
/// Configuration for stable element identity metadata.
pub struct ElementIdConfig<'render> {
    pub(crate) id_name: Option<OwnedOrRef<'render, String>>,
    pub(crate) handle: ElementId,
}

impl<'render> ElementIdConfig<'render> {
    /// Creates an id config with a user-provided name and a generated handle.
    pub fn new_named(id_name: OwnedOrRef<'render, String>) -> Self {
        Self {
            id_name: Some(id_name),
            handle: ElementId::new(),
        }
    }

    /// Creates an unnamed id config with a generated handle.
    pub fn new_empty() -> Self {
        Self {
            id_name: None,
            handle: ElementId::new(),
        }
    }

    /// Returns the optional id name.
    pub fn get_id_name(&self) -> Option<&String> {
        Some(self.id_name.as_ref()?.as_ref())
    }

    /// Returns the generated [`ElementId`] handle.
    pub fn get_handle(&self) -> ElementId {
        self.handle
    }
}

#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
#[cfg_attr(
    feature = "serialization",
    derive(serde::Serialize, serde::Deserialize)
)]
/// Opaque element identifier generated by Cotis.
pub struct ElementId {
    static_id: u128,
}

static RAND_GEN_STATE: Mutex<Option<(u128, u128)>> = Mutex::new(None);

impl ElementId {
    fn new() -> Self {
        let mut guard = RAND_GEN_STATE.lock().unwrap();
        let state = match *guard {
            Some(s) => s,
            None => {
                // Proper PCG setup (inc is odd, state is warmed up)
                let rng = Rand64::new(0xDEAD_BEEF_CAFE_BABE);
                let s = rng.state();
                *guard = Some(s);
                s
            }
        };
        let mut rand = Rand64::from_state(state);
        let static_id = rand.rand_u64() as u128 + ((rand.rand_u64() as u128) << 64);
        *guard = Some(rand.state());
        ElementId { static_id }
    }

    /// Returns the raw identifier value.
    ///
    /// This is mostly useful for serialization, logging, or external indexing.
    pub fn get_id(&self) -> u128 {
        self.static_id
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::sync::Arc;

    struct StrWrapper(String);
    impl AsRef<String> for StrWrapper {
        fn as_ref(&self) -> &String {
            &self.0
        }
    }

    #[test]
    fn owned_or_ref_variants_resolve_via_as_ref() {
        let owned = OwnedOrRef::Owned(Box::new(7u32));
        assert_eq!(*owned.as_ref(), 7);

        let value = 9u32;
        let borrowed = OwnedOrRef::Ref(&value);
        assert_eq!(*borrowed.as_ref(), 9);

        let wrapper = StrWrapper(String::from("cotis"));
        let trait_ref: &(dyn AsRef<String> + Send + Sync) = &wrapper;
        let as_ref = OwnedOrRef::AsRef(trait_ref);
        assert_eq!(as_ref.as_ref(), "cotis");

        let arc: Arc<dyn AsRef<str> + Send + Sync> = Arc::new(String::from("shared"));
        let as_owned = OwnedOrRef::AsOwnedRef(arc);
        assert_eq!(as_owned.as_ref(), "shared");
    }

    #[test]
    fn share_clone_promotes_owned_to_shared_arc() {
        let mut owned = OwnedOrRef::Owned(Box::new(String::from("cotis")));
        let shared = owned.share_clone();
        assert_eq!(owned.as_ref(), "cotis");
        assert_eq!(shared.as_ref(), "cotis");
    }

    #[test]
    fn owned_or_ref_mut_reborrow_updates_underlying_value() {
        let mut value = 10;
        let mut wrapper = OwnedOrRefMut::Ref(&mut value);
        {
            let mut reborrowed = wrapper.reborrow();
            *reborrowed.as_mut() += 5;
        }
        assert_eq!(value, 15);
    }

    #[test]
    fn element_id_config_named_and_empty() {
        let named = ElementIdConfig::new_named(OwnedOrRef::Owned(Box::new(String::from("panel"))));
        assert_eq!(named.get_id_name(), Some(&String::from("panel")));
        assert_ne!(named.get_handle().get_id(), 0);

        let empty = ElementIdConfig::new_empty();
        assert_eq!(empty.get_id_name(), None);
        assert_ne!(empty.get_handle().get_id(), 0);
    }

    #[test]
    fn element_ids_are_unique() {
        let first = ElementIdConfig::new_empty().get_handle();
        let second = ElementIdConfig::new_empty().get_handle();
        assert_ne!(first, second);
    }

    #[cfg(feature = "serialization")]
    mod serialization_tests {
        use super::*;
        use serde_json;

        #[test]
        fn owned_string_roundtrips_through_json() {
            let value = OwnedOrRef::Owned(Box::new(String::from("cotis")));
            let json = serde_json::to_string(&value).unwrap();
            let decoded: OwnedOrRef<'_, String> = serde_json::from_str(&json).unwrap();
            assert_eq!(decoded.as_ref(), "cotis");
        }

        #[test]
        fn owned_str_roundtrips_through_json() {
            let arc: Arc<dyn AsRef<str> + Send + Sync> = Arc::new(String::from("hello"));
            let value: OwnedOrRef<'_, str> = OwnedOrRef::AsOwnedRef(arc);
            let json = serde_json::to_string(&value).unwrap();
            let decoded: OwnedOrRef<'_, str> = serde_json::from_str(&json).unwrap();
            assert_eq!(decoded.as_ref(), "hello");
        }
    }
}