use serde::{Deserialize, Serialize};
use std::collections::{HashMap, VecDeque};
use std::time::SystemTime;
const MAX_ACCESS_LOG_ENTRIES: usize = 10_000;
#[derive(Debug, Clone, Default)]
pub struct SharedMemory {
pub(super) data: HashMap<String, MemoryEntry>,
pub(super) access_log: VecDeque<MemoryAccess>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MemoryEntry {
pub key: String,
pub value: String,
pub created_by: String,
pub created_at: u64,
pub modified_by: Option<String>,
pub modified_at: Option<u64>,
pub access_count: u32,
pub tags: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct MemoryAccess {
pub key: String,
pub agent_id: String,
pub action: MemoryAction,
pub timestamp: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MemoryAction {
Read,
Write,
Delete,
}
impl SharedMemory {
pub fn new() -> Self {
Self::default()
}
pub fn write(
&mut self,
key: impl Into<String>,
value: impl Into<String>,
agent_id: impl Into<String>,
) {
let key = key.into();
let agent_id = agent_id.into();
let now = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
if let Some(entry) = self.data.get_mut(&key) {
entry.value = value.into();
entry.modified_by = Some(agent_id.clone());
entry.modified_at = Some(now);
} else {
self.data.insert(
key.clone(),
MemoryEntry {
key: key.clone(),
value: value.into(),
created_by: agent_id.clone(),
created_at: now,
modified_by: None,
modified_at: None,
access_count: 0,
tags: Vec::new(),
},
);
}
self.access_log.push_back(MemoryAccess {
key,
agent_id,
action: MemoryAction::Write,
timestamp: now,
});
if self.access_log.len() > MAX_ACCESS_LOG_ENTRIES {
self.access_log.pop_front();
}
}
pub fn read(&mut self, key: &str, agent_id: impl Into<String>) -> Option<String> {
let now = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
if let Some(entry) = self.data.get_mut(key) {
entry.access_count += 1;
self.access_log.push_back(MemoryAccess {
key: key.to_string(),
agent_id: agent_id.into(),
action: MemoryAction::Read,
timestamp: now,
});
if self.access_log.len() > MAX_ACCESS_LOG_ENTRIES {
self.access_log.pop_front();
}
Some(entry.value.clone())
} else {
None
}
}
pub fn peek(&self, key: &str) -> Option<&str> {
self.data.get(key).map(|e| e.value.as_str())
}
pub fn delete(&mut self, key: &str, agent_id: impl Into<String>) -> Option<String> {
let now = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
self.access_log.push_back(MemoryAccess {
key: key.to_string(),
agent_id: agent_id.into(),
action: MemoryAction::Delete,
timestamp: now,
});
if self.access_log.len() > MAX_ACCESS_LOG_ENTRIES {
self.access_log.pop_front();
}
self.data.remove(key).map(|e| e.value)
}
pub fn keys(&self) -> Vec<&str> {
self.data.keys().map(|k| k.as_str()).collect()
}
pub fn entries(&self) -> Vec<&MemoryEntry> {
self.data.values().collect()
}
pub fn tag(&mut self, key: &str, tag: impl Into<String>) {
if let Some(entry) = self.data.get_mut(key) {
entry.tags.push(tag.into());
}
}
pub fn find_by_tag(&self, tag: &str) -> Vec<&MemoryEntry> {
self.data
.values()
.filter(|e| e.tags.contains(&tag.to_string()))
.collect()
}
pub fn access_log(&self) -> &VecDeque<MemoryAccess> {
&self.access_log
}
pub fn clear(&mut self) {
self.data.clear();
self.access_log.clear();
}
}
#[cfg(test)]
#[path = "../../../tests/unit/orchestration/swarm/memory/memory_test.rs"]
mod tests;