use serde_json::Value;
use std::collections::{BTreeMap, BTreeSet};
use std::sync::{Arc, Mutex, OnceLock};
use crate::ports::ActorTurnError;
use crate::workflow::machine::{CommandKind, RunCommand};
pub type ActorTurnOpen =
Arc<dyn Fn(&Value) -> std::result::Result<String, ActorTurnError> + Send + Sync>;
pub type ActorTurnRun =
Arc<dyn Fn(&str, &Value) -> std::result::Result<Value, ActorTurnError> + Send + Sync>;
pub type ActorTurnCancel = Arc<dyn Fn(&str) -> TurnCancelDisposition + Send + Sync>;
pub struct ActorTurnPort {
pub open: ActorTurnOpen,
pub run: ActorTurnRun,
pub cancel: ActorTurnCancel,
pub abandon: Arc<dyn Fn(&str) + Send + Sync>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum TurnCancelDisposition {
Accepted,
NoActiveTurn,
Unavailable,
}
impl TurnCancelDisposition {
pub fn as_str(self) -> &'static str {
match self {
Self::Accepted => "accepted",
Self::NoActiveTurn => "no-active-turn",
Self::Unavailable => "unavailable",
}
}
}
type LiveTurnTable = Mutex<BTreeMap<(String, String), (String, Option<String>)>>;
fn live_turns() -> &'static LiveTurnTable {
static TURNS: OnceLock<LiveTurnTable> = OnceLock::new();
TURNS.get_or_init(|| Mutex::new(BTreeMap::new()))
}
pub struct LiveTurnRegistration {
run_id: String,
command_id: String,
attempt_token: String,
handle: Option<String>,
}
impl LiveTurnRegistration {
pub fn reserve(run_id: &str, command_id: &str, attempt_token: &str) -> Self {
let mut turns = live_turns()
.lock()
.unwrap_or_else(|poison| poison.into_inner());
turns.insert(
(run_id.to_owned(), command_id.to_owned()),
(attempt_token.to_owned(), None),
);
Self {
run_id: run_id.to_owned(),
command_id: command_id.to_owned(),
attempt_token: attempt_token.to_owned(),
handle: None,
}
}
pub fn attach(&mut self, handle: &str) {
let mut turns = live_turns()
.lock()
.unwrap_or_else(|poison| poison.into_inner());
let key = (self.run_id.clone(), self.command_id.clone());
let owned = turns
.get(&key)
.is_some_and(|(token, _)| token == &self.attempt_token);
if owned || !turns.contains_key(&key) {
turns.insert(key, (self.attempt_token.clone(), Some(handle.to_owned())));
}
self.handle = Some(handle.to_owned());
}
}
impl Drop for LiveTurnRegistration {
fn drop(&mut self) {
let mut turns = live_turns()
.lock()
.unwrap_or_else(|poison| poison.into_inner());
let key = (self.run_id.clone(), self.command_id.clone());
if turns
.get(&key)
.is_some_and(|(token, _)| token == &self.attempt_token)
{
turns.remove(&key);
}
}
}
pub enum LiveTurnOwner {
Absent,
Preparing,
Ready(String),
}
pub fn live_turn_owner(run_id: &str, command_id: &str, attempt_token: &str) -> LiveTurnOwner {
let turns = live_turns()
.lock()
.unwrap_or_else(|poison| poison.into_inner());
match turns.get(&(run_id.to_owned(), command_id.to_owned())) {
Some((token, Some(handle))) if token == attempt_token => {
LiveTurnOwner::Ready(handle.clone())
}
Some((token, None)) if token == attempt_token => LiveTurnOwner::Preparing,
_ => LiveTurnOwner::Absent,
}
}
pub type RunWake = Arc<dyn Fn(&str, &str, &Value) -> std::result::Result<(), String> + Send + Sync>;
pub struct RunWakePort {
pub wake: RunWake,
}
fn cancelled_runs() -> &'static Mutex<BTreeSet<String>> {
static RUNS: OnceLock<Mutex<BTreeSet<String>>> = OnceLock::new();
RUNS.get_or_init(|| Mutex::new(BTreeSet::new()))
}
pub fn mark_run_cancelled(run_id: &str) {
cancelled_runs()
.lock()
.unwrap_or_else(|poison| poison.into_inner())
.insert(run_id.to_owned());
}
pub fn unmark_run_cancelled(run_id: &str) {
cancelled_runs()
.lock()
.unwrap_or_else(|poison| poison.into_inner())
.remove(run_id);
}
pub fn cancellation_pending(run_id: &str) -> bool {
cancelled_runs()
.lock()
.unwrap_or_else(|poison| poison.into_inner())
.contains(run_id)
}
fn driving_runs() -> &'static Mutex<BTreeSet<String>> {
static RUNS: OnceLock<Mutex<BTreeSet<String>>> = OnceLock::new();
RUNS.get_or_init(|| Mutex::new(BTreeSet::new()))
}
pub struct DriveReservation {
run_id: String,
}
impl DriveReservation {
pub fn acquire(run_id: &str) -> Option<Self> {
let mut driving = driving_runs()
.lock()
.unwrap_or_else(|poison| poison.into_inner());
driving.insert(run_id.to_owned()).then(|| Self {
run_id: run_id.to_owned(),
})
}
pub fn is_active(run_id: &str) -> bool {
driving_runs()
.lock()
.unwrap_or_else(|poison| poison.into_inner())
.contains(run_id)
}
}
impl Drop for DriveReservation {
fn drop(&mut self) {
driving_runs()
.lock()
.unwrap_or_else(|poison| poison.into_inner())
.remove(&self.run_id);
}
}
pub struct EntryTurnRegistration {
slot_id: String,
handle: String,
params: Value,
port: Arc<ActorTurnPort>,
armed: bool,
}
impl EntryTurnRegistration {
pub fn new(slot_id: String, handle: String, params: Value, port: Arc<ActorTurnPort>) -> Self {
Self {
slot_id,
handle,
params,
port,
armed: true,
}
}
pub fn ad_hoc(handle: String, params: Value, port: Arc<ActorTurnPort>) -> Self {
Self::new(String::new(), handle, params, port)
}
pub fn matches(&self, command: &RunCommand) -> bool {
!self.slot_id.is_empty()
&& matches!(command.kind, CommandKind::Actor | CommandKind::WorksetItem)
&& command.binding_id.as_deref() == Some(self.slot_id.as_str())
&& command.state_visit == 1
&& command.binding_ordinal == 0
}
pub fn into_run(mut self) -> (String, Value) {
self.armed = false;
(self.handle.clone(), self.params.clone())
}
}
impl Drop for EntryTurnRegistration {
fn drop(&mut self) {
if self.armed {
(self.port.abandon)(&self.handle);
}
}
}