use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use chrono::{DateTime, Utc};
use crate::spatial::{RegionBounds, RegionCoordinate, WorldCoordinate};
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
pub struct ServerId(pub String);
impl ServerId {
pub fn new() -> Self {
Self(uuid::Uuid::new_v4().to_string())
}
pub fn from_string(s: impl Into<String>) -> Self {
Self(s.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl std::fmt::Display for ServerId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<String> for ServerId {
fn from(s: String) -> Self {
Self(s)
}
}
impl From<&str> for ServerId {
fn from(s: &str) -> Self {
Self(s.to_string())
}
}
#[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>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiServerRegistration {
pub name: String,
pub address: String,
pub region_coord: RegionCoordinate,
pub center: WorldCoordinate,
pub bounds: f64,
pub capacity: u32,
#[serde(default)]
pub version: String,
#[serde(default)]
pub metadata: HashMap<String, serde_json::Value>,
}
impl ApiServerRegistration {
pub fn from_bounds(
name: String,
address: String,
region_coord: RegionCoordinate,
bounds: &RegionBounds,
capacity: u32,
) -> Self {
Self {
name,
address,
region_coord,
center: bounds.center(),
bounds: bounds.half_extent(),
capacity,
version: String::new(),
metadata: HashMap::new(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiRegistrationResponse {
pub success: bool,
pub server_id: String,
pub message: String,
pub heartbeat_interval_secs: u32,
#[serde(default)]
pub adjacent_servers: Vec<AdjacentServerInfo>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AdjacentServerInfo {
pub server_id: String,
pub address: String,
pub region_coord: RegionCoordinate,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiServerHeartbeat {
pub server_id: String,
pub current_connections: u32,
pub load: f32,
pub accepting_connections: bool,
#[serde(default)]
pub avg_tick_ms: f64,
#[serde(default)]
pub memory_bytes: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiHeartbeatResponse {
pub success: bool,
pub message: String,
#[serde(default)]
pub commands: Vec<ServerCommand>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum ServerCommand {
PrepareShutdown { deadline_secs: u32 },
ConfigUpdate { config: serde_json::Value },
HealthCheck,
}
#[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);
}
}