use std::collections::HashMap;
use std::sync::Arc;
use shared_memory::Shmem;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ShmVariableConfig {
pub name: String, pub gm_name: String, pub offset: usize,
pub size: usize,
#[serde(rename = "type")] pub data_type: String,
pub direction: String,
}
pub struct ShmContext {
#[allow(dead_code)] shmem: Shmem,
pointers: HashMap<String, usize>, }
unsafe impl Send for ShmContext {}
unsafe impl Sync for ShmContext {}
impl std::fmt::Debug for ShmContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ShmContext")
.field("shm_id", &self.shmem.get_os_id())
.field("pointer_count", &self.pointers.len())
.finish()
}
}
impl ShmContext {
pub fn new(os_id: &str, configs: Vec<ShmVariableConfig>) -> Result<Self, Box<dyn std::error::Error>> {
let shmem = shared_memory::ShmemConf::new().os_id(os_id).open()?;
let mut pointers = HashMap::new();
for config in configs {
if config.offset + config.size > shmem.len() {
continue;
}
pointers.insert(config.name.clone(), config.offset);
}
Ok(Self { shmem, pointers })
}
pub unsafe fn get_pointer(&self, name: &str) -> Option<*mut u8> {
self.pointers.get(name).map(|&offset| unsafe { self.shmem.as_ptr().add(offset) })
}
pub fn resolve(self: &Arc<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::with_context(map, Arc::clone(self))
}
}
#[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
}
}
unsafe impl Send for ShmPtr {}
unsafe impl Sync for ShmPtr {}
#[derive(Debug, Clone)]
pub struct ShmMap {
pointers: Arc<HashMap<String, ShmPtr>>,
_context: Option<Arc<ShmContext>>,
}
impl ShmMap {
pub fn new(pointers: HashMap<String, ShmPtr>) -> Self {
Self {
pointers: Arc::new(pointers),
_context: None,
}
}
pub fn with_context(pointers: HashMap<String, ShmPtr>, context: Arc<ShmContext>) -> Self {
Self {
pointers: Arc::new(pointers),
_context: Some(context),
}
}
pub fn get<T>(&self, name: &str) -> Option<*mut T> {
self.pointers.get(name).map(|p| p.as_ptr())
}
pub fn get_raw(&self, name: &str) -> Option<usize> {
self.pointers.get(name).map(|p| p.0)
}
pub unsafe fn read<T: Copy>(&self, name: &str) -> Option<T> {
self.get::<T>(name).map(|ptr| unsafe { *ptr })
}
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
}
}
pub fn contains(&self, name: &str) -> bool {
self.pointers.contains_key(name)
}
pub fn len(&self) -> usize {
self.pointers.len()
}
pub fn is_empty(&self) -> bool {
self.pointers.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = (&String, &ShmPtr)> {
self.pointers.iter()
}
}
#[cfg(test)]
mod tests {
use super::*;
use shared_memory::ShmemConf;
#[test]
fn shmmap_survives_original_context_drop() {
let owner = ShmemConf::new()
.size(64)
.create()
.expect("create SHM segment");
let os_id = owner.get_os_id().to_string();
let configs = vec![ShmVariableConfig {
name: "flag".to_string(),
gm_name: "gm.flag".to_string(),
offset: 0,
size: 1,
data_type: "bool".to_string(),
direction: "read".to_string(),
}];
let ctx = Arc::new(ShmContext::new(&os_id, configs).expect("open SHM"));
let map = ctx.resolve(&["flag".to_string()]);
drop(ctx);
unsafe { assert!(map.write::<u8>("flag", 0xA5)); }
let observed = unsafe { *owner.as_ptr() };
assert_eq!(observed, 0xA5);
}
}