Skip to main content

Horizon_Network_Common/
messages.rs

1//! Message types for inter-service communication.
2//!
3//! These types define the messages exchanged between Horizon, Atlas, and Maestro
4//! for coordination and control.
5
6use serde::{Deserialize, Serialize};
7
8use crate::health::{HealthCheck, HealthCheckRequest};
9use crate::player::{PlayerId, PlayerState, DisconnectReason};
10use crate::server::{
11    ServerHeartbeat, ServerInfo, ServerRegistration, ServerId,
12    RegistrationResponse, SpawnServerRequest, SpawnServerResponse,
13};
14use crate::transfer::{TransferRequest, TransferToken};
15use crate::spatial::WorldCoordinate;
16
17/// Messages sent from Horizon to Atlas.
18#[derive(Debug, Clone, Serialize, Deserialize)]
19#[serde(tag = "type", content = "payload")]
20pub enum HorizonMessage {
21    /// Server registration request
22    Register(ServerRegistration),
23    
24    /// Periodic heartbeat
25    Heartbeat(ServerHeartbeat),
26    
27    /// Health check response
28    HealthResponse(HealthCheck),
29    
30    /// Player connected notification
31    PlayerConnected {
32        player_id: PlayerId,
33        position: WorldCoordinate,
34    },
35    
36    /// Player disconnected notification
37    PlayerDisconnected {
38        player_id: PlayerId,
39        reason: DisconnectReason,
40    },
41    
42    /// Player position update
43    PlayerPositionUpdate {
44        player_id: PlayerId,
45        position: WorldCoordinate,
46        velocity: WorldCoordinate,
47    },
48    
49    /// Request to transfer a player (player approaching boundary)
50    TransferRequest(TransferRequest),
51    
52    /// Transfer completed successfully
53    TransferComplete {
54        player_id: PlayerId,
55        success: bool,
56        error: Option<String>,
57    },
58    
59    /// Player accepted from transfer
60    TransferAccepted {
61        player_id: PlayerId,
62        token_id: String,
63    },
64    
65    /// Server shutting down
66    Shutdown {
67        server_id: ServerId,
68        player_count: u32,
69    },
70}
71
72/// Messages sent from Atlas to Horizon.
73#[derive(Debug, Clone, Serialize, Deserialize)]
74#[serde(tag = "type", content = "payload")]
75pub enum AtlasMessage {
76    /// Registration response
77    RegistrationResponse(RegistrationResponse),
78    
79    /// Health check request
80    HealthCheckRequest(HealthCheckRequest),
81    
82    /// Initiate player transfer
83    InitiateTransfer {
84        player_id: PlayerId,
85        target_server: ServerInfo,
86        token: TransferToken,
87    },
88    
89    /// Accept incoming transfer
90    AcceptTransfer {
91        token: TransferToken,
92        player_state: PlayerState,
93    },
94    
95    /// Cancel pending transfer
96    CancelTransfer {
97        player_id: PlayerId,
98        reason: String,
99    },
100    
101    /// Prepare for shutdown
102    PrepareShutdown {
103        deadline_secs: u32,
104    },
105    
106    /// Update adjacent servers list
107    AdjacentServersUpdate {
108        servers: Vec<ServerInfo>,
109    },
110    
111    /// Configuration update
112    ConfigUpdate {
113        config: serde_json::Value,
114    },
115}
116
117/// Messages sent from Atlas to Maestro.
118#[derive(Debug, Clone, Serialize, Deserialize)]
119#[serde(tag = "type", content = "payload")]
120pub enum AtlasToMaestroMessage {
121    /// Request to spawn a new server instance
122    SpawnServer(SpawnServerRequest),
123    
124    /// Request to stop a server instance
125    StopServer {
126        instance_id: String,
127        graceful: bool,
128    },
129    
130    /// Request server health/stats
131    GetServerStats {
132        instance_id: String,
133    },
134    
135    /// Scale cluster to target count
136    ScaleCluster {
137        target_count: u32,
138    },
139}
140
141/// Messages sent from Maestro to Atlas.
142#[derive(Debug, Clone, Serialize, Deserialize)]
143#[serde(tag = "type", content = "payload")]
144pub enum MaestroMessage {
145    /// Server spawn response
146    SpawnResponse(SpawnServerResponse),
147    
148    /// Server stopped notification
149    ServerStopped {
150        instance_id: String,
151        exit_code: Option<i32>,
152    },
153    
154    /// Server stats response
155    ServerStats {
156        instance_id: String,
157        cpu_percent: f32,
158        memory_mb: u32,
159        running: bool,
160    },
161    
162    /// Cluster scaled
163    ClusterScaled {
164        current_count: u32,
165        target_count: u32,
166    },
167    
168    /// Error occurred
169    Error {
170        operation: String,
171        message: String,
172    },
173}
174
175/// Wrapper for all message types with metadata.
176#[derive(Debug, Clone, Serialize, Deserialize)]
177pub struct Envelope<T> {
178    /// Message ID for tracking
179    pub id: String,
180    /// Timestamp in milliseconds since epoch
181    pub timestamp_ms: u64,
182    /// Source service identifier
183    pub source: String,
184    /// Destination service identifier
185    pub destination: String,
186    /// The actual message
187    pub message: T,
188}
189
190impl<T> Envelope<T> {
191    /// Creates a new envelope with the given message.
192    pub fn new(source: impl Into<String>, destination: impl Into<String>, message: T) -> Self {
193        use std::time::{SystemTime, UNIX_EPOCH};
194        
195        Self {
196            id: uuid::Uuid::new_v4().to_string(),
197            timestamp_ms: SystemTime::now()
198                .duration_since(UNIX_EPOCH)
199                .unwrap()
200                .as_millis() as u64,
201            source: source.into(),
202            destination: destination.into(),
203            message,
204        }
205    }
206}
207
208/// Simple acknowledgment response.
209#[derive(Debug, Clone, Serialize, Deserialize)]
210pub struct Ack {
211    /// Message ID being acknowledged
212    pub message_id: String,
213    /// Whether the message was processed successfully
214    pub success: bool,
215    /// Optional error message
216    #[serde(default)]
217    pub error: Option<String>,
218}
219
220impl Ack {
221    /// Creates a successful acknowledgment.
222    pub fn success(message_id: impl Into<String>) -> Self {
223        Self {
224            message_id: message_id.into(),
225            success: true,
226            error: None,
227        }
228    }
229
230    /// Creates a failed acknowledgment.
231    pub fn failure(message_id: impl Into<String>, error: impl Into<String>) -> Self {
232        Self {
233            message_id: message_id.into(),
234            success: false,
235            error: Some(error.into()),
236        }
237    }
238}
239
240#[cfg(test)]
241mod tests {
242    use super::*;
243
244    #[test]
245    fn test_horizon_message_serialization() {
246        let msg = HorizonMessage::PlayerConnected {
247            player_id: PlayerId::new(),
248            position: WorldCoordinate::new(100.0, 50.0, -200.0),
249        };
250        let json = serde_json::to_string(&msg).unwrap();
251        let restored: HorizonMessage = serde_json::from_str(&json).unwrap();
252        match restored {
253            HorizonMessage::PlayerConnected { position, .. } => {
254                assert!((position.x - 100.0).abs() < 0.001);
255            }
256            _ => panic!("Wrong message type"),
257        }
258    }
259
260    #[test]
261    fn test_envelope_creation() {
262        let msg = Ack::success("test-123");
263        let envelope = Envelope::new("horizon-1", "atlas", msg);
264        assert_eq!(envelope.source, "horizon-1");
265        assert_eq!(envelope.destination, "atlas");
266    }
267}