117 lines
3.8 KiB
Rust
117 lines
3.8 KiB
Rust
|
|
//! Read/write helpers for the `active_agents` LWW-map collection.
|
||
|
|
//!
|
||
|
|
//! Active-agent entries are keyed by `agent_id` and track which story and node
|
||
|
|
//! an agent is currently working on, along with when it started.
|
||
|
|
|
||
|
|
use bft_json_crdt::json_crdt::{JsonValue, *};
|
||
|
|
use bft_json_crdt::op::ROOT_ID;
|
||
|
|
use serde_json::json;
|
||
|
|
|
||
|
|
use super::super::state::{apply_and_persist, get_crdt, rebuild_active_agent_index};
|
||
|
|
use super::super::types::{ActiveAgentCrdt, ActiveAgentView};
|
||
|
|
use super::list_id_at;
|
||
|
|
|
||
|
|
/// Write or update an active-agent entry keyed by `agent_id`.
|
||
|
|
pub fn write_active_agent(agent_id: &str, story_id: &str, node_id: &str, started_at: f64) {
|
||
|
|
let Some(state_mutex) = get_crdt() else {
|
||
|
|
return;
|
||
|
|
};
|
||
|
|
let Ok(mut state) = state_mutex.lock() else {
|
||
|
|
return;
|
||
|
|
};
|
||
|
|
|
||
|
|
if let Some(&idx) = state.active_agent_index.get(agent_id) {
|
||
|
|
apply_and_persist(&mut state, |s| {
|
||
|
|
s.crdt.doc.active_agents[idx]
|
||
|
|
.story_id
|
||
|
|
.set(story_id.to_string())
|
||
|
|
});
|
||
|
|
apply_and_persist(&mut state, |s| {
|
||
|
|
s.crdt.doc.active_agents[idx]
|
||
|
|
.node_id
|
||
|
|
.set(node_id.to_string())
|
||
|
|
});
|
||
|
|
apply_and_persist(&mut state, |s| {
|
||
|
|
s.crdt.doc.active_agents[idx].started_at.set(started_at)
|
||
|
|
});
|
||
|
|
} else {
|
||
|
|
let entry: JsonValue = json!({
|
||
|
|
"agent_id": agent_id,
|
||
|
|
"story_id": story_id,
|
||
|
|
"node_id": node_id,
|
||
|
|
"started_at": started_at,
|
||
|
|
})
|
||
|
|
.into();
|
||
|
|
apply_and_persist(&mut state, |s| {
|
||
|
|
s.crdt.doc.active_agents.insert(ROOT_ID, entry)
|
||
|
|
});
|
||
|
|
state.active_agent_index = rebuild_active_agent_index(&state.crdt);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Read all active-agent entries.
|
||
|
|
pub fn read_all_active_agents() -> Option<Vec<ActiveAgentView>> {
|
||
|
|
let state_mutex = get_crdt()?;
|
||
|
|
let state = state_mutex.lock().ok()?;
|
||
|
|
let mut out = Vec::new();
|
||
|
|
for entry in state.crdt.doc.active_agents.iter() {
|
||
|
|
if let Some(v) = extract_active_agent_view(entry) {
|
||
|
|
out.push(v);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
Some(out)
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Read a single active-agent entry by `agent_id`.
|
||
|
|
pub fn read_active_agent(agent_id: &str) -> Option<ActiveAgentView> {
|
||
|
|
let state_mutex = get_crdt()?;
|
||
|
|
let state = state_mutex.lock().ok()?;
|
||
|
|
let &idx = state.active_agent_index.get(agent_id)?;
|
||
|
|
extract_active_agent_view(&state.crdt.doc.active_agents[idx])
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Tombstone an active-agent entry by `agent_id`.
|
||
|
|
pub fn delete_active_agent(agent_id: &str) -> bool {
|
||
|
|
let Some(state_mutex) = get_crdt() else {
|
||
|
|
return false;
|
||
|
|
};
|
||
|
|
let Ok(mut state) = state_mutex.lock() else {
|
||
|
|
return false;
|
||
|
|
};
|
||
|
|
let Some(&idx) = state.active_agent_index.get(agent_id) else {
|
||
|
|
return false;
|
||
|
|
};
|
||
|
|
let Some(op_id) = list_id_at(&state.crdt.doc.active_agents, idx) else {
|
||
|
|
return false;
|
||
|
|
};
|
||
|
|
apply_and_persist(&mut state, |s| s.crdt.doc.active_agents.delete(op_id));
|
||
|
|
state.active_agent_index = rebuild_active_agent_index(&state.crdt);
|
||
|
|
true
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Convert a CRDT active-agent entry into its read-only view representation.
|
||
|
|
pub(super) fn extract_active_agent_view(entry: &ActiveAgentCrdt) -> Option<ActiveAgentView> {
|
||
|
|
let agent_id = match entry.agent_id.view() {
|
||
|
|
JsonValue::String(s) if !s.is_empty() => s,
|
||
|
|
_ => return None,
|
||
|
|
};
|
||
|
|
let story_id = match entry.story_id.view() {
|
||
|
|
JsonValue::String(s) if !s.is_empty() => Some(s),
|
||
|
|
_ => None,
|
||
|
|
};
|
||
|
|
let node_id = match entry.node_id.view() {
|
||
|
|
JsonValue::String(s) if !s.is_empty() => Some(s),
|
||
|
|
_ => None,
|
||
|
|
};
|
||
|
|
let started_at = match entry.started_at.view() {
|
||
|
|
JsonValue::Number(n) => n,
|
||
|
|
_ => 0.0,
|
||
|
|
};
|
||
|
|
Some(ActiveAgentView {
|
||
|
|
agent_id,
|
||
|
|
story_id,
|
||
|
|
node_id,
|
||
|
|
started_at,
|
||
|
|
})
|
||
|
|
}
|