Horizon_Network_Common/
server.rs

1//! Server registration and status types.
2//!
3//! These types are used for Horizon instances to register with Atlas
4//! and report their status and availability.
5
6use serde::{Deserialize, Serialize};
7use uuid::Uuid;
8use chrono::{DateTime, Utc};
9
10use crate::spatial::{RegionBounds, RegionCoordinate, WorldCoordinate};
11
12/// Unique identifier for a Horizon server instance.
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
14pub struct ServerId(pub Uuid);
15
16impl ServerId {
17    /// Creates a new random server ID.
18    pub fn new() -> Self {
19        Self(Uuid::new_v4())
20    }
21
22    /// Creates a server ID from a string.
23    pub fn from_str(s: &str) -> Result<Self, uuid::Error> {
24        Uuid::parse_str(s).map(Self)
25    }
26}
27
28impl Default for ServerId {
29    fn default() -> Self {
30        Self::new()
31    }
32}
33
34impl std::fmt::Display for ServerId {
35    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36        write!(f, "{}", self.0)
37    }
38}
39
40/// Current status of a Horizon server instance.
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
42#[serde(rename_all = "snake_case")]
43pub enum ServerStatus {
44    /// Server is starting up
45    Starting,
46    /// Server is running and accepting connections
47    Running,
48    /// Server is draining connections (preparing to shutdown)
49    Draining,
50    /// Server is stopped
51    Stopped,
52    /// Server encountered an error
53    Error,
54}
55
56impl Default for ServerStatus {
57    fn default() -> Self {
58        Self::Starting
59    }
60}
61
62/// Basic server information for registration and discovery.
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct ServerInfo {
65    /// Unique server identifier
66    pub id: ServerId,
67    /// Human-readable server name
68    pub name: String,
69    /// Server address (host:port)
70    pub address: String,
71    /// Region coordinate in the world grid
72    pub region_coord: RegionCoordinate,
73    /// Spatial bounds of this server's region
74    pub bounds: RegionBounds,
75    /// Center point of the region in world coordinates
76    pub center: WorldCoordinate,
77    /// Maximum number of connections this server can handle
78    pub capacity: u32,
79    /// Server version string
80    pub version: String,
81}
82
83impl ServerInfo {
84    /// Creates new server info with the given parameters.
85    pub fn new(
86        name: String,
87        address: String,
88        region_coord: RegionCoordinate,
89        bounds: RegionBounds,
90        capacity: u32,
91    ) -> Self {
92        Self {
93            id: ServerId::new(),
94            name,
95            address,
96            region_coord,
97            bounds,
98            center: bounds.center(),
99            capacity,
100            version: env!("CARGO_PKG_VERSION").to_string(),
101        }
102    }
103}
104
105/// Server registration request sent from Horizon to Atlas.
106#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct ServerRegistration {
108    /// Server information
109    pub server: ServerInfo,
110    /// Current server status
111    pub status: ServerStatus,
112    /// Timestamp of registration
113    pub registered_at: DateTime<Utc>,
114    /// Optional metadata for custom properties
115    #[serde(default)]
116    pub metadata: std::collections::HashMap<String, serde_json::Value>,
117}
118
119impl ServerRegistration {
120    /// Creates a new server registration.
121    pub fn new(server: ServerInfo) -> Self {
122        Self {
123            server,
124            status: ServerStatus::Starting,
125            registered_at: Utc::now(),
126            metadata: std::collections::HashMap::new(),
127        }
128    }
129}
130
131/// Server heartbeat sent periodically from Horizon to Atlas.
132#[derive(Debug, Clone, Serialize, Deserialize)]
133pub struct ServerHeartbeat {
134    /// Server ID
135    pub server_id: ServerId,
136    /// Current server status
137    pub status: ServerStatus,
138    /// Current number of connected players
139    pub current_connections: u32,
140    /// Server load (0.0 to 1.0)
141    pub load: f32,
142    /// Timestamp of this heartbeat
143    pub timestamp: DateTime<Utc>,
144    /// Average tick time in milliseconds
145    #[serde(default)]
146    pub avg_tick_ms: f64,
147    /// Memory usage in bytes
148    #[serde(default)]
149    pub memory_bytes: u64,
150}
151
152impl ServerHeartbeat {
153    /// Creates a new heartbeat with current metrics.
154    pub fn new(
155        server_id: ServerId,
156        status: ServerStatus,
157        current_connections: u32,
158        capacity: u32,
159    ) -> Self {
160        let load = if capacity > 0 {
161            current_connections as f32 / capacity as f32
162        } else {
163            0.0
164        };
165
166        Self {
167            server_id,
168            status,
169            current_connections,
170            load,
171            timestamp: Utc::now(),
172            avg_tick_ms: 0.0,
173            memory_bytes: 0,
174        }
175    }
176}
177
178/// Response from Atlas when a server registers.
179#[derive(Debug, Clone, Serialize, Deserialize)]
180pub struct RegistrationResponse {
181    /// Whether registration was successful
182    pub success: bool,
183    /// Assigned server ID (may differ from requested if conflict)
184    pub server_id: ServerId,
185    /// Message describing the result
186    pub message: String,
187    /// Heartbeat interval in seconds
188    pub heartbeat_interval_secs: u32,
189    /// List of adjacent servers for cross-region communication
190    #[serde(default)]
191    pub adjacent_servers: Vec<ServerInfo>,
192}
193
194/// Request from Atlas to Maestro to spawn a new Horizon instance.
195#[derive(Debug, Clone, Serialize, Deserialize)]
196pub struct SpawnServerRequest {
197    /// Requested region coordinate
198    pub region_coord: RegionCoordinate,
199    /// Region bounds for the new server
200    pub bounds: RegionBounds,
201    /// Optional preferred name
202    pub name: Option<String>,
203    /// Environment variables to pass to the container
204    #[serde(default)]
205    pub environment: std::collections::HashMap<String, String>,
206}
207
208/// Response from Maestro after spawning a server.
209#[derive(Debug, Clone, Serialize, Deserialize)]
210pub struct SpawnServerResponse {
211    /// Whether spawn was successful
212    pub success: bool,
213    /// Container/instance ID
214    pub instance_id: String,
215    /// Server address once running
216    pub address: Option<String>,
217    /// Error message if failed
218    pub error: Option<String>,
219}
220
221#[cfg(test)]
222mod tests {
223    use super::*;
224
225    #[test]
226    fn test_server_registration() {
227        let info = ServerInfo::new(
228            "test-server".to_string(),
229            "127.0.0.1:8080".to_string(),
230            RegionCoordinate::center(),
231            RegionBounds::default(),
232            100,
233        );
234        let reg = ServerRegistration::new(info);
235        assert_eq!(reg.status, ServerStatus::Starting);
236    }
237
238    #[test]
239    fn test_heartbeat_load() {
240        let heartbeat = ServerHeartbeat::new(
241            ServerId::new(),
242            ServerStatus::Running,
243            50,
244            100,
245        );
246        assert!((heartbeat.load - 0.5).abs() < 0.001);
247    }
248}