use serde::{Deserialize, Serialize};
use uuid::Uuid;
use chrono::{DateTime, Utc};
use crate::spatial::{RegionBounds, RegionCoordinate, WorldCoordinate};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ServerId(pub Uuid);
impl ServerId {
pub fn new() -> Self {
Self(Uuid::new_v4())
}
pub fn from_str(s: &str) -> Result<Self, uuid::Error> {
Uuid::parse_str(s).map(Self)
}
}
impl Default for ServerId {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Display for ServerId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ServerStatus {
Starting,
Running,
Draining,
Stopped,
Error,
}
impl Default for ServerStatus {
fn default() -> Self {
Self::Starting
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerInfo {
pub id: ServerId,
pub name: String,
pub address: String,
pub region_coord: RegionCoordinate,
pub bounds: RegionBounds,
pub center: WorldCoordinate,
pub capacity: u32,
pub version: String,
}
impl ServerInfo {
pub fn new(
name: String,
address: String,
region_coord: RegionCoordinate,
bounds: RegionBounds,
capacity: u32,
) -> Self {
Self {
id: ServerId::new(),
name,
address,
region_coord,
bounds,
center: bounds.center(),
capacity,
version: env!("CARGO_PKG_VERSION").to_string(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerRegistration {
pub server: ServerInfo,
pub status: ServerStatus,
pub registered_at: DateTime<Utc>,
#[serde(default)]
pub metadata: std::collections::HashMap<String, serde_json::Value>,
}
impl ServerRegistration {
pub fn new(server: ServerInfo) -> Self {
Self {
server,
status: ServerStatus::Starting,
registered_at: Utc::now(),
metadata: std::collections::HashMap::new(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerHeartbeat {
pub server_id: ServerId,
pub status: ServerStatus,
pub current_connections: u32,
pub load: f32,
pub timestamp: DateTime<Utc>,
#[serde(default)]
pub avg_tick_ms: f64,
#[serde(default)]
pub memory_bytes: u64,
}
impl ServerHeartbeat {
pub fn new(
server_id: ServerId,
status: ServerStatus,
current_connections: u32,
capacity: u32,
) -> Self {
let load = if capacity > 0 {
current_connections as f32 / capacity as f32
} else {
0.0
};
Self {
server_id,
status,
current_connections,
load,
timestamp: Utc::now(),
avg_tick_ms: 0.0,
memory_bytes: 0,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RegistrationResponse {
pub success: bool,
pub server_id: ServerId,
pub message: String,
pub heartbeat_interval_secs: u32,
#[serde(default)]
pub adjacent_servers: Vec<ServerInfo>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SpawnServerRequest {
pub region_coord: RegionCoordinate,
pub bounds: RegionBounds,
pub name: Option<String>,
#[serde(default)]
pub environment: std::collections::HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SpawnServerResponse {
pub success: bool,
pub instance_id: String,
pub address: Option<String>,
pub error: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_server_registration() {
let info = ServerInfo::new(
"test-server".to_string(),
"127.0.0.1:8080".to_string(),
RegionCoordinate::center(),
RegionBounds::default(),
100,
);
let reg = ServerRegistration::new(info);
assert_eq!(reg.status, ServerStatus::Starting);
}
#[test]
fn test_heartbeat_load() {
let heartbeat = ServerHeartbeat::new(
ServerId::new(),
ServerStatus::Running,
50,
100,
);
assert!((heartbeat.load - 0.5).abs() < 0.001);
}
}