use crate::node::node_name_atom;
use serde::{Deserialize, Serialize};
use starlang_atom::Atom;
use std::fmt;
use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
static PID_COUNTER: AtomicU64 = AtomicU64::new(0);
static CREATION: AtomicU32 = AtomicU32::new(0);
#[derive(Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Pid {
node: Atom,
id: u64,
creation: u32,
}
impl Pid {
pub fn new() -> Self {
Self {
node: node_name_atom(),
id: PID_COUNTER.fetch_add(1, Ordering::Relaxed),
creation: CREATION.load(Ordering::Relaxed),
}
}
pub fn from_parts_atom(node: Atom, id: u64, creation: u32) -> Self {
Self { node, id, creation }
}
pub fn remote(node_name: &str, id: u64, creation: u32) -> Self {
Self {
node: Atom::new(node_name),
id,
creation,
}
}
#[inline]
pub fn node(&self) -> Atom {
self.node
}
#[inline]
pub fn node_name(&self) -> String {
self.node.as_str()
}
#[inline]
pub const fn id(&self) -> u64 {
self.id
}
#[inline]
pub const fn creation(&self) -> u32 {
self.creation
}
#[inline]
pub fn is_local(&self) -> bool {
self.node == node_name_atom()
}
}
pub fn increment_creation() -> u32 {
CREATION.fetch_add(1, Ordering::SeqCst) + 1
}
pub fn current_creation() -> u32 {
CREATION.load(Ordering::Relaxed)
}
impl Default for Pid {
fn default() -> Self {
Self::new()
}
}
impl fmt::Debug for Pid {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_local() {
write!(f, "Pid<0.{}.{}>", self.id, self.creation)
} else {
write!(f, "Pid<{}.{}.{}>", self.node, self.id, self.creation)
}
}
}
impl fmt::Display for Pid {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_local() {
write!(f, "<0.{}.{}>", self.id, self.creation)
} else {
write!(f, "<{}.{}.{}>", self.node, self.id, self.creation)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use starlang_atom::atom;
#[test]
fn test_pid_uniqueness() {
let pid1 = Pid::new();
let pid2 = Pid::new();
assert_ne!(pid1, pid2);
}
#[test]
fn test_pid_local() {
let pid = Pid::new();
assert!(pid.is_local());
}
#[test]
fn test_pid_remote() {
let pid = Pid::remote("node2@localhost", 100, 2);
assert_eq!(pid.node_name(), "node2@localhost");
assert_eq!(pid.id(), 100);
assert_eq!(pid.creation(), 2);
assert!(!pid.is_local());
}
#[test]
fn test_pid_from_parts_atom() {
let node = atom!("test@host");
let pid = Pid::from_parts_atom(node, 42, 1);
assert_eq!(pid.node(), node);
assert_eq!(pid.id(), 42);
assert_eq!(pid.creation(), 1);
}
#[test]
fn test_pid_display_local() {
let pid = Pid::new();
let display = format!("{}", pid);
assert!(
display.starts_with("<0."),
"expected local display format, got: {}",
display
);
let parts: Vec<&str> = display
.trim_matches(|c| c == '<' || c == '>')
.split('.')
.collect();
assert_eq!(parts.len(), 3, "expected 3 parts in PID display");
assert_eq!(parts[0], "0", "expected node 0 for local PID");
}
#[test]
fn test_pid_display_remote() {
let pid = Pid::remote("node2@host", 42, 0);
assert_eq!(format!("{}", pid), "<node2@host.42.0>");
assert_eq!(format!("{:?}", pid), "Pid<node2@host.42.0>");
}
#[test]
fn test_pid_serialization() {
let pid = Pid::remote("node1@localhost", 123, 5);
let bytes = postcard::to_allocvec(&pid).unwrap();
let decoded: Pid = postcard::from_bytes(&bytes).unwrap();
assert_eq!(pid, decoded);
assert_eq!(decoded.node_name(), "node1@localhost");
}
#[test]
fn test_pid_hash() {
use std::collections::HashSet;
let mut set = HashSet::new();
let pid1 = Pid::new();
let pid2 = Pid::new();
set.insert(pid1);
set.insert(pid2);
set.insert(pid1);
assert_eq!(set.len(), 2);
}
#[test]
fn test_creation_distinguishes_pids() {
let node = atom!("test@host");
let pid1 = Pid::from_parts_atom(node, 42, 0);
let pid2 = Pid::from_parts_atom(node, 42, 1);
assert_ne!(pid1, pid2);
}
#[test]
fn test_increment_creation() {
let before = current_creation();
let new_creation = increment_creation();
assert_eq!(new_creation, before + 1);
assert_eq!(current_creation(), new_creation);
}
}