use std::fmt::Debug;
use std::hash::Hash;
use serde::{Deserialize, Serialize};
pub trait AgentId: Clone + Debug + Eq + Hash + Send + Sync + Serialize + for<'de> Deserialize<'de> + 'static {}
impl<T> AgentId for T where T: Clone + Debug + Eq + Hash + Send + Sync + Serialize + for<'de> Deserialize<'de> + 'static {}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(bound = "")]
pub enum Agent<ID: AgentId> {
Human { id: ID, name: String },
Bot { id: ID, name: String },
System,
}
impl<ID: AgentId> Agent<ID> {
pub fn new_human(id: ID, name: impl Into<String>) -> Self {
Agent::Human { id, name: name.into() }
}
pub fn new_bot(id: ID, name: impl Into<String>) -> Self {
Agent::Bot { id, name: name.into() }
}
pub fn system() -> Self {
Agent::System
}
pub fn id(&self) -> Option<&ID> {
match self {
Agent::Human { id, .. } | Agent::Bot { id, .. } => Some(id),
Agent::System => None,
}
}
pub fn id_cloned(&self) -> Option<ID> {
self.id().cloned()
}
pub fn label(&self) -> String {
match self {
Agent::Human { name, .. } | Agent::Bot { name, .. } => name.clone(),
Agent::System => "SYSTEM".to_string(),
}
}
pub fn is_system(&self) -> bool {
matches!(self, Agent::System)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SnapshotData<SnapshotPayload> {
pub payload: SnapshotPayload,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(bound(
serialize = "Op: Serialize, SnapshotPayload: Serialize",
deserialize = "Op: Deserialize<'de>, SnapshotPayload: Deserialize<'de>"
))]
pub enum SessionMessage<Op, ID: AgentId, SnapshotPayload> {
Ops { from: Agent<ID>, ops: Vec<Op> },
StateData {
from: Agent<ID>,
data: SnapshotData<SnapshotPayload>,
},
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
enum TestOp {
Move { x: i32 },
}
#[test]
fn the_envelope_encodes_as_one_document_with_ops_as_objects() {
let msg: SessionMessage<TestOp, u32, ()> = SessionMessage::Ops {
from: Agent::new_human(7, "player"),
ops: vec![TestOp::Move { x: 3 }],
};
let json = serde_json::to_string(&msg).unwrap();
assert!(json.contains(r#""Move""#), "ops are readable in the document: {json}");
assert!(!json.contains("[[") && !json.contains("[1"), "no byte arrays: {json}");
}
#[test]
fn it_round_trips() {
let msg: SessionMessage<TestOp, u32, ()> = SessionMessage::Ops {
from: Agent::new_human(1, "a"),
ops: vec![TestOp::Move { x: -2 }],
};
let bytes = serde_json::to_vec(&msg).unwrap();
let back: SessionMessage<TestOp, u32, ()> = serde_json::from_slice(&bytes).unwrap();
match back {
SessionMessage::Ops { from, ops } => {
assert_eq!(from, Agent::new_human(1, "a"));
assert_eq!(ops, vec![TestOp::Move { x: -2 }]);
}
other => panic!("wrong variant: {other:?}"),
}
}
#[test]
fn the_system_agent_has_no_id_and_still_names_itself() {
let system: Agent<u32> = Agent::system();
assert_eq!(system.id(), None);
assert_eq!(system.label(), "SYSTEM");
assert!(system.is_system());
}
}