Skip to main content

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 std::collections::HashMap;
8use chrono::{DateTime, Utc};
9
10use crate::spatial::{RegionBounds, RegionCoordinate, WorldCoordinate};
11
12/// Unique identifier for a Horizon server instance.
13/// Uses String for JSON API compatibility.
14#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
15pub struct ServerId(pub String);
16
17impl ServerId {
18    /// Creates a new random server ID using UUID v4.
19    pub fn new() -> Self {
20        Self(uuid::Uuid::new_v4().to_string())
21    }
22
23    /// Creates a server ID from a string.
24    pub fn from_string(s: impl Into<String>) -> Self {
25        Self(s.into())
26    }
27
28    /// Gets the inner string value.
29    pub fn as_str(&self) -> &str {
30        &self.0
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
40impl From<String> for ServerId {
41    fn from(s: String) -> Self {
42        Self(s)
43    }
44}
45
46impl From<&str> for ServerId {
47    fn from(s: &str) -> Self {
48        Self(s.to_string())
49    }
50}
51
52/// Current status of a Horizon server instance.
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
54#[serde(rename_all = "snake_case")]
55pub enum ServerStatus {
56    /// Server is starting up
57    Starting,
58    /// Server is running and accepting connections
59    Running,
60    /// Server is draining connections (preparing to shutdown)
61    Draining,
62    /// Server is stopped
63    Stopped,
64    /// Server encountered an error
65    Error,
66}
67
68impl Default for ServerStatus {
69    fn default() -> Self {
70        Self::Starting
71    }
72}
73
74/// Basic server information for registration and discovery.
75#[derive(Debug, Clone, Serialize, Deserialize)]
76pub struct ServerInfo {
77    /// Unique server identifier
78    pub id: ServerId,
79    /// Human-readable server name
80    pub name: String,
81    /// Server address (host:port)
82    pub address: String,
83    /// Region coordinate in the world grid
84    pub region_coord: RegionCoordinate,
85    /// Spatial bounds of this server's region
86    pub bounds: RegionBounds,
87    /// Center point of the region in world coordinates
88    pub center: WorldCoordinate,
89    /// Maximum number of connections this server can handle
90    pub capacity: u32,
91    /// Server version string
92    pub version: String,
93}
94
95impl ServerInfo {
96    /// Creates new server info with the given parameters.
97    pub fn new(
98        name: String,
99        address: String,
100        region_coord: RegionCoordinate,
101        bounds: RegionBounds,
102        capacity: u32,
103    ) -> Self {
104        Self {
105            id: ServerId::new(),
106            name,
107            address,
108            region_coord,
109            bounds,
110            center: bounds.center(),
111            capacity,
112            version: env!("CARGO_PKG_VERSION").to_string(),
113        }
114    }
115}
116
117/// Server registration request sent from Horizon to Atlas.
118#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct ServerRegistration {
120    /// Server information
121    pub server: ServerInfo,
122    /// Current server status
123    pub status: ServerStatus,
124    /// Timestamp of registration
125    pub registered_at: DateTime<Utc>,
126    /// Optional metadata for custom properties
127    #[serde(default)]
128    pub metadata: std::collections::HashMap<String, serde_json::Value>,
129}
130
131impl ServerRegistration {
132    /// Creates a new server registration.
133    pub fn new(server: ServerInfo) -> Self {
134        Self {
135            server,
136            status: ServerStatus::Starting,
137            registered_at: Utc::now(),
138            metadata: std::collections::HashMap::new(),
139        }
140    }
141}
142
143/// Server heartbeat sent periodically from Horizon to Atlas.
144#[derive(Debug, Clone, Serialize, Deserialize)]
145pub struct ServerHeartbeat {
146    /// Server ID
147    pub server_id: ServerId,
148    /// Current server status
149    pub status: ServerStatus,
150    /// Current number of connected players
151    pub current_connections: u32,
152    /// Server load (0.0 to 1.0)
153    pub load: f32,
154    /// Timestamp of this heartbeat
155    pub timestamp: DateTime<Utc>,
156    /// Average tick time in milliseconds
157    #[serde(default)]
158    pub avg_tick_ms: f64,
159    /// Memory usage in bytes
160    #[serde(default)]
161    pub memory_bytes: u64,
162}
163
164impl ServerHeartbeat {
165    /// Creates a new heartbeat with current metrics.
166    pub fn new(
167        server_id: ServerId,
168        status: ServerStatus,
169        current_connections: u32,
170        capacity: u32,
171    ) -> Self {
172        let load = if capacity > 0 {
173            current_connections as f32 / capacity as f32
174        } else {
175            0.0
176        };
177
178        Self {
179            server_id,
180            status,
181            current_connections,
182            load,
183            timestamp: Utc::now(),
184            avg_tick_ms: 0.0,
185            memory_bytes: 0,
186        }
187    }
188}
189
190/// Response from Atlas when a server registers.
191#[derive(Debug, Clone, Serialize, Deserialize)]
192pub struct RegistrationResponse {
193    /// Whether registration was successful
194    pub success: bool,
195    /// Assigned server ID (may differ from requested if conflict)
196    pub server_id: ServerId,
197    /// Message describing the result
198    pub message: String,
199    /// Heartbeat interval in seconds
200    pub heartbeat_interval_secs: u32,
201    /// List of adjacent servers for cross-region communication
202    #[serde(default)]
203    pub adjacent_servers: Vec<ServerInfo>,
204}
205
206/// Request from Atlas to Maestro to spawn a new Horizon instance.
207#[derive(Debug, Clone, Serialize, Deserialize)]
208pub struct SpawnServerRequest {
209    /// Requested region coordinate
210    pub region_coord: RegionCoordinate,
211    /// Region bounds for the new server
212    pub bounds: RegionBounds,
213    /// Optional preferred name
214    pub name: Option<String>,
215    /// Environment variables to pass to the container
216    #[serde(default)]
217    pub environment: std::collections::HashMap<String, String>,
218}
219
220/// Response from Maestro after spawning a server.
221#[derive(Debug, Clone, Serialize, Deserialize)]
222pub struct SpawnServerResponse {
223    /// Whether spawn was successful
224    pub success: bool,
225    /// Container/instance ID
226    pub instance_id: String,
227    /// Server address once running
228    pub address: Option<String>,
229    /// Error message if failed
230    pub error: Option<String>,
231}
232
233/// Simplified server registration for REST API.
234/// This is what Horizon sends to Atlas when registering.
235#[derive(Debug, Clone, Serialize, Deserialize)]
236pub struct ApiServerRegistration {
237    /// Server name
238    pub name: String,
239    /// Server address (host:port)
240    pub address: String,
241    /// Region X coordinate
242    pub region_coord: RegionCoordinate,
243    /// Center point of the region
244    pub center: WorldCoordinate,
245    /// Region bounds (half-extent)
246    pub bounds: f64,
247    /// Maximum capacity
248    pub capacity: u32,
249    /// Server version
250    #[serde(default)]
251    pub version: String,
252    /// Additional metadata
253    #[serde(default)]
254    pub metadata: HashMap<String, serde_json::Value>,
255}
256
257impl ApiServerRegistration {
258    /// Create from RegionBounds
259    pub fn from_bounds(
260        name: String,
261        address: String,
262        region_coord: RegionCoordinate,
263        bounds: &RegionBounds,
264        capacity: u32,
265    ) -> Self {
266        Self {
267            name,
268            address,
269            region_coord,
270            center: bounds.center(),
271            bounds: bounds.half_extent(),
272            capacity,
273            version: String::new(),
274            metadata: HashMap::new(),
275        }
276    }
277}
278
279/// API response when a server registers.
280#[derive(Debug, Clone, Serialize, Deserialize)]
281pub struct ApiRegistrationResponse {
282    pub success: bool,
283    pub server_id: String,
284    pub message: String,
285    pub heartbeat_interval_secs: u32,
286    #[serde(default)]
287    pub adjacent_servers: Vec<AdjacentServerInfo>,
288}
289
290/// Adjacent server info for the API.
291#[derive(Debug, Clone, Serialize, Deserialize)]
292pub struct AdjacentServerInfo {
293    pub server_id: String,
294    pub address: String,
295    pub region_coord: RegionCoordinate,
296}
297
298/// API heartbeat request.
299#[derive(Debug, Clone, Serialize, Deserialize)]
300pub struct ApiServerHeartbeat {
301    pub server_id: String,
302    pub current_connections: u32,
303    pub load: f32,
304    pub accepting_connections: bool,
305    #[serde(default)]
306    pub avg_tick_ms: f64,
307    #[serde(default)]
308    pub memory_bytes: u64,
309}
310
311/// API heartbeat response.
312#[derive(Debug, Clone, Serialize, Deserialize)]
313pub struct ApiHeartbeatResponse {
314    pub success: bool,
315    pub message: String,
316    #[serde(default)]
317    pub commands: Vec<ServerCommand>,
318}
319
320/// Commands from Atlas to Horizon.
321#[derive(Debug, Clone, Serialize, Deserialize)]
322#[serde(tag = "type")]
323pub enum ServerCommand {
324    PrepareShutdown { deadline_secs: u32 },
325    ConfigUpdate { config: serde_json::Value },
326    HealthCheck,
327}
328
329#[cfg(test)]
330mod tests {
331    use super::*;
332
333    #[test]
334    fn test_server_registration() {
335        let info = ServerInfo::new(
336            "test-server".to_string(),
337            "127.0.0.1:8080".to_string(),
338            RegionCoordinate::center(),
339            RegionBounds::default(),
340            100,
341        );
342        let reg = ServerRegistration::new(info);
343        assert_eq!(reg.status, ServerStatus::Starting);
344    }
345
346    #[test]
347    fn test_heartbeat_load() {
348        let heartbeat = ServerHeartbeat::new(
349            ServerId::new(),
350            ServerStatus::Running,
351            50,
352            100,
353        );
354        assert!((heartbeat.load - 0.5).abs() < 0.001);
355    }
356}