use serde::{Serialize, Deserialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct AgentId(pub u64);
impl AgentId {
pub const SYSTEM: Self = Self(0);
pub const USER: Self = Self(1);
pub const AGENT_START: Self = Self(100);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct AgentPriority(pub u32);
impl AgentPriority {
pub const LOW: Self = Self(0);
pub const NORMAL: Self = Self(100);
pub const HIGH: Self = Self(1000);
pub const CRITICAL: Self = Self(u32::MAX);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MutationMetadata {
pub agent_id: AgentId,
pub priority: AgentPriority,
pub timestamp_ms: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ConflictResolution {
#[default]
LastWriterWins,
PriorityWins,
Merge,
}
thread_local! {
static CURRENT_AGENT: std::cell::RefCell<Option<MutationMetadata>> = const { std::cell::RefCell::new(None) };
}
pub fn with_agent<F, R>(agent_id: AgentId, priority: AgentPriority, f: F) -> R
where
F: FnOnce() -> R,
{
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as u64;
let meta = MutationMetadata {
agent_id,
priority,
timestamp_ms: now,
};
let prev = CURRENT_AGENT.with(|a| a.replace(Some(meta)));
let result = f();
CURRENT_AGENT.with(|a| a.replace(prev));
result
}
pub struct AgentTransaction<F> {
pub agent_id: AgentId,
pub priority: AgentPriority,
pub mutation: F,
}
impl<F, R> AgentTransaction<F>
where
F: FnOnce() -> R,
{
pub fn new(agent_id: AgentId, priority: AgentPriority, mutation: F) -> Self {
Self { agent_id, priority, mutation }
}
pub fn execute(self) -> R {
with_agent(self.agent_id, self.priority, self.mutation)
}
}
#[derive(Debug, Clone)]
pub struct ConflictEvent {
pub agent_id: AgentId,
pub priority: AgentPriority,
pub existing_agent_id: AgentId,
pub existing_priority: AgentPriority,
pub timestamp_ms: u64,
}
type ConflictHandlerList = std::sync::Arc<std::sync::Mutex<Vec<Box<dyn Fn(ConflictEvent) + Send + Sync>>>>;
static CONFLICT_HANDLERS: once_cell::sync::Lazy<ConflictHandlerList> =
once_cell::sync::Lazy::new(|| std::sync::Arc::new(std::sync::Mutex::new(Vec::new())));
pub fn on_conflict<F>(handler: F)
where
F: Fn(ConflictEvent) + Send + Sync + 'static,
{
CONFLICT_HANDLERS.lock().unwrap().push(Box::new(handler));
}
pub(crate) fn notify_conflict(event: ConflictEvent) {
let handlers = CONFLICT_HANDLERS.lock().unwrap();
for handler in handlers.iter() {
handler(event.clone());
}
}
pub trait AgentSurface: Send + Sync {
fn agent_id(&self) -> AgentId;
fn query<T: Clone + Send + Sync + 'static>(&self, state: &crate::State<T>) -> T;
fn update<T: Clone + Send + Sync + 'static>(
&self,
state: &crate::State<T>,
value: T,
priority: AgentPriority,
);
fn transact<R, F>(&self, priority: AgentPriority, f: F) -> R
where
F: FnOnce() -> R;
}
pub struct DefaultAgentSurface {
id: AgentId,
}
impl DefaultAgentSurface {
pub fn new(id: AgentId) -> Self {
Self { id }
}
}
impl AgentSurface for DefaultAgentSurface {
fn agent_id(&self) -> AgentId {
self.id
}
fn query<T: Clone + Send + Sync + 'static>(&self, state: &crate::State<T>) -> T {
state.get()
}
fn update<T: Clone + Send + Sync + 'static>(
&self,
state: &crate::State<T>,
value: T,
priority: AgentPriority,
) {
with_agent(self.id, priority, || {
state.set(value);
});
}
fn transact<R, F>(&self, priority: AgentPriority, f: F) -> R
where
F: FnOnce() -> R,
{
with_agent(self.id, priority, f)
}
}
pub(crate) fn get_current_mutation_metadata() -> Option<MutationMetadata> {
CURRENT_AGENT.with(|a| *a.borrow())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::State;
#[test]
fn test_conflict_resolution() {
let state = State::new(0).with_resolution(ConflictResolution::PriorityWins);
let surface_a = DefaultAgentSurface::new(AgentId(101));
let surface_b = DefaultAgentSurface::new(AgentId(102));
surface_a.update(&state, 10, AgentPriority::NORMAL);
assert_eq!(state.get(), 10);
surface_b.update(&state, 20, AgentPriority::LOW);
assert_eq!(state.get(), 10);
surface_b.update(&state, 30, AgentPriority::HIGH);
assert_eq!(state.get(), 30);
}
}