use serde::{Deserialize, Serialize};
use crate::health::{HealthCheck, HealthCheckRequest};
use crate::player::{PlayerId, PlayerState, DisconnectReason};
use crate::server::{
ServerHeartbeat, ServerInfo, ServerRegistration, ServerId,
RegistrationResponse, SpawnServerRequest, SpawnServerResponse,
};
use crate::transfer::{TransferRequest, TransferToken};
use crate::spatial::WorldCoordinate;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", content = "payload")]
pub enum HorizonMessage {
Register(ServerRegistration),
Heartbeat(ServerHeartbeat),
HealthResponse(HealthCheck),
PlayerConnected {
player_id: PlayerId,
position: WorldCoordinate,
},
PlayerDisconnected {
player_id: PlayerId,
reason: DisconnectReason,
},
PlayerPositionUpdate {
player_id: PlayerId,
position: WorldCoordinate,
velocity: WorldCoordinate,
},
TransferRequest(TransferRequest),
TransferComplete {
player_id: PlayerId,
success: bool,
error: Option<String>,
},
TransferAccepted {
player_id: PlayerId,
token_id: String,
},
Shutdown {
server_id: ServerId,
player_count: u32,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", content = "payload")]
pub enum AtlasMessage {
RegistrationResponse(RegistrationResponse),
HealthCheckRequest(HealthCheckRequest),
InitiateTransfer {
player_id: PlayerId,
target_server: ServerInfo,
token: TransferToken,
},
AcceptTransfer {
token: TransferToken,
player_state: PlayerState,
},
CancelTransfer {
player_id: PlayerId,
reason: String,
},
PrepareShutdown {
deadline_secs: u32,
},
AdjacentServersUpdate {
servers: Vec<ServerInfo>,
},
ConfigUpdate {
config: serde_json::Value,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", content = "payload")]
pub enum AtlasToMaestroMessage {
SpawnServer(SpawnServerRequest),
StopServer {
instance_id: String,
graceful: bool,
},
GetServerStats {
instance_id: String,
},
ScaleCluster {
target_count: u32,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", content = "payload")]
pub enum MaestroMessage {
SpawnResponse(SpawnServerResponse),
ServerStopped {
instance_id: String,
exit_code: Option<i32>,
},
ServerStats {
instance_id: String,
cpu_percent: f32,
memory_mb: u32,
running: bool,
},
ClusterScaled {
current_count: u32,
target_count: u32,
},
Error {
operation: String,
message: String,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Envelope<T> {
pub id: String,
pub timestamp_ms: u64,
pub source: String,
pub destination: String,
pub message: T,
}
impl<T> Envelope<T> {
pub fn new(source: impl Into<String>, destination: impl Into<String>, message: T) -> Self {
use std::time::{SystemTime, UNIX_EPOCH};
Self {
id: uuid::Uuid::new_v4().to_string(),
timestamp_ms: SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis() as u64,
source: source.into(),
destination: destination.into(),
message,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Ack {
pub message_id: String,
pub success: bool,
#[serde(default)]
pub error: Option<String>,
}
impl Ack {
pub fn success(message_id: impl Into<String>) -> Self {
Self {
message_id: message_id.into(),
success: true,
error: None,
}
}
pub fn failure(message_id: impl Into<String>, error: impl Into<String>) -> Self {
Self {
message_id: message_id.into(),
success: false,
error: Some(error.into()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_horizon_message_serialization() {
let msg = HorizonMessage::PlayerConnected {
player_id: PlayerId::new(),
position: WorldCoordinate::new(100.0, 50.0, -200.0),
};
let json = serde_json::to_string(&msg).unwrap();
let restored: HorizonMessage = serde_json::from_str(&json).unwrap();
match restored {
HorizonMessage::PlayerConnected { position, .. } => {
assert!((position.x - 100.0).abs() < 0.001);
}
_ => panic!("Wrong message type"),
}
}
#[test]
fn test_envelope_creation() {
let msg = Ack::success("test-123");
let envelope = Envelope::new("horizon-1", "atlas", msg);
assert_eq!(envelope.source, "horizon-1");
assert_eq!(envelope.destination, "atlas");
}
}