mechutil 0.8.1

Utility structures and functions for mechatronics applications.
Documentation
use std::collections::HashMap;
use std::sync::Arc;
use shared_memory::Shmem;
use serde::{Deserialize, Serialize};

/// Layout configuration for a single variable (received from Server)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ShmVariableConfig {
    pub name: String,      // Local name (e.g., "holding_0")
    pub gm_name: String,   // Global Memory name
    pub offset: usize,
    pub size: usize,
    #[serde(rename = "type")] // Handle "type" keyword in JSON
    pub data_type: String,
    pub direction: String,
}

/// Manages the shared memory connection and pointers
pub struct ShmContext {
    #[allow(dead_code)] // Keep shmem alive
    shmem: Shmem,
    pointers: HashMap<String, usize>, // variable_name -> offset
}

impl ShmContext {
    pub fn new(os_id: &str, configs: Vec<ShmVariableConfig>) -> Result<Self, Box<dyn std::error::Error>> {
        // Open existing SHM (created by Server)
        let shmem = shared_memory::ShmemConf::new().os_id(os_id).open()?;
        let mut pointers = HashMap::new();

        for config in configs {
            // Validate bounds (optional but recommended)
            if config.offset + config.size > shmem.len() {
                // Log warning or error
                continue;
            }
            pointers.insert(config.name.clone(), config.offset);
        }

        Ok(Self { shmem, pointers })
    }

    /// Get a raw pointer to a variable.
    /// UNSAFE: Caller must ensure thread safety and type correctness.
    pub unsafe fn get_pointer(&self, name: &str) -> Option<*mut u8> {
        self.pointers.get(name).map(|&offset| unsafe { self.shmem.as_ptr().add(offset) })
    }

    /// Resolve a list of variable names to an `ShmMap` of pointers.
    ///
    /// Names not found in the layout are silently skipped.
    pub fn resolve(&self, names: &[String]) -> ShmMap {
        let mut map = HashMap::new();
        for name in names {
            if let Some(ptr) = unsafe { self.get_pointer(name) } {
                map.insert(name.clone(), ShmPtr::new(ptr));
            }
        }
        ShmMap::new(map)
    }
}

/// Wrapper for a shared-memory pointer, stored as `usize` for Send+Sync safety.
#[derive(Debug, Clone, Copy)]
pub struct ShmPtr(pub usize);

impl ShmPtr {
    pub fn new<T>(ptr: *mut T) -> Self {
        Self(ptr as usize)
    }

    pub fn as_ptr<T>(&self) -> *mut T {
        self.0 as *mut T
    }
}

// SAFETY: ShmPtr stores an address as usize (no raw pointer) — Send+Sync is safe.
unsafe impl Send for ShmPtr {}
unsafe impl Sync for ShmPtr {}

/// A typed map of shared-memory variable names to their resolved pointers.
#[derive(Debug, Clone)]
pub struct ShmMap {
    pointers: Arc<HashMap<String, ShmPtr>>,
}

impl ShmMap {
    pub fn new(pointers: HashMap<String, ShmPtr>) -> Self {
        Self {
            pointers: Arc::new(pointers),
        }
    }

    /// Get a typed pointer to a variable.
    pub fn get<T>(&self, name: &str) -> Option<*mut T> {
        self.pointers.get(name).map(|p| p.as_ptr())
    }

    /// Get the raw address of a variable.
    pub fn get_raw(&self, name: &str) -> Option<usize> {
        self.pointers.get(name).map(|p| p.0)
    }

    /// Read a value from shared memory.
    ///
    /// # Safety
    /// Caller must ensure `T` matches the type of data at the pointer.
    pub unsafe fn read<T: Copy>(&self, name: &str) -> Option<T> {
        self.get::<T>(name).map(|ptr| unsafe { *ptr })
    }

    /// Write a value to shared memory.
    ///
    /// # Safety
    /// Caller must ensure `T` matches the type of data at the pointer.
    pub unsafe fn write<T: Copy>(&self, name: &str, value: T) -> bool {
        if let Some(ptr) = self.get::<T>(name) {
            unsafe { *ptr = value };
            true
        } else {
            false
        }
    }

    /// Check if a variable name exists in the map.
    pub fn contains(&self, name: &str) -> bool {
        self.pointers.contains_key(name)
    }

    /// Return the number of resolved pointers.
    pub fn len(&self) -> usize {
        self.pointers.len()
    }

    /// Return true if no pointers were resolved.
    pub fn is_empty(&self) -> bool {
        self.pointers.is_empty()
    }

    /// Iterate over all (name, pointer) entries.
    pub fn iter(&self) -> impl Iterator<Item = (&String, &ShmPtr)> {
        self.pointers.iter()
    }
}