pub mod components; mod entity_factory; mod entity_system; mod id_manager; mod loader; mod random; mod game_logic_service; mod game_service; mod world_client; mod spatial_grid; use dotenv::dotenv; use std::env; use std::sync::Arc; use hecs::World; use tokio::time::{timeout, Duration}; use tokio::sync::oneshot; use tonic::transport::Server; use tracing::{debug, error, info, warn}; use utils::service_discovery::{get_kube_service_endpoints_by_dns, get_service_endpoints_by_dns}; use utils::{health_check, logging}; use loader::load_zone_for_map; use crate::components::position::Position; use crate::entity_factory::EntityFactory; use crate::entity_system::EntitySystem; use crate::world_client::{WorldGameLogicServiceImpl, WorldGameLogicServiceServer}; use crate::game_logic_service::{MyGameLogicService, game_logic::game_logic_service_server::GameLogicServiceServer}; use crate::game_service::{MyGameService}; #[tokio::main] async fn main() -> Result<(), Box> { dotenv().ok(); let app_name = env!("CARGO_PKG_NAME"); logging::setup_logging(app_name, &["game_logic_service", "health_check"]); let map_id = env::var("MAP_ID").unwrap_or_else(|_| "20".to_string()).parse::()?; let file_path = "/opt/data/zone_data.json"; // // Set the gRPC server address let addr = env::var("LISTEN_ADDR").unwrap_or_else(|_| "0.0.0.0".to_string()); let port = env::var("SERVICE_PORT").unwrap_or_else(|_| "50056".to_string()); let db_url = format!( "http://{}", get_kube_service_endpoints_by_dns("database-service", "tcp", "database-service") .await? .get(0) .unwrap() ); let chat_service = format!( "http://{}", get_kube_service_endpoints_by_dns("chat-service", "tcp", "chat-service") .await? .get(0) .unwrap() ); // Create world service first let world_game_logic_service_impl = WorldGameLogicServiceImpl::new(map_id); let world_game_logic_service_arc = Arc::new(world_game_logic_service_impl); // Create shared entity system with world service let entity_system = Arc::new(EntitySystem::new().with_world_service(world_game_logic_service_arc.clone())); // Create world service with entity system for the gRPC server let world_game_logic_service = WorldGameLogicServiceImpl::new(map_id).with_entity_system(entity_system.clone()); // Create gRPC services let game_logic_service = MyGameLogicService { map_id, entity_system: entity_system.clone(), }; let game_service = MyGameService {}; // Start gRPC server with graceful shutdown support let grpc_addr = format!("{}:{}", addr, port).parse()?; info!("Starting Game Logic Service gRPC server on {}", grpc_addr); // Create shutdown signal channels let (shutdown_tx, shutdown_rx) = oneshot::channel::<()>(); let (game_logic_shutdown_tx, mut game_logic_shutdown_rx) = oneshot::channel::<()>(); let server_task = tokio::spawn(async move { let server = Server::builder() .add_service(GameLogicServiceServer::new(game_logic_service)) .add_service(game_service.into_service()) .add_service(WorldGameLogicServiceServer::new(world_game_logic_service)) .serve_with_shutdown(grpc_addr, async { shutdown_rx.await.ok(); info!("gRPC server shutdown signal received"); }); if let Err(e) = server.await { error!("gRPC server error: {}", e); } else { info!("gRPC server shut down gracefully"); } }); let entity_system_clone = entity_system.clone(); let game_logic_task = tokio::spawn(async move { let mut loading = true; loop { // Check for shutdown signal if let Ok(_) = game_logic_shutdown_rx.try_recv() { info!("Game logic task received shutdown signal"); break; } // Load the map if loading { match load_zone_for_map(file_path, map_id) { Ok(Some(zone)) => { info!("Zone with Map Id {} found:", map_id); entity_system_clone.load_map(zone); loading = false; } Ok(None) => { error!("No zone found for Map Id {}", map_id); std::thread::sleep(std::time::Duration::from_secs(1)); } Err(e) => { error!("Error loading zone: {}", e); std::thread::sleep(std::time::Duration::from_secs(1)); } } } // Update the world if !loading { entity_system_clone.run(); } std::thread::sleep(std::time::Duration::from_millis(1000/30)); } info!("Game logic task shut down gracefully"); }); // Register service with Consul // health_check::start_health_check(addr.as_str()).await?; // Wait for shutdown signal info!("Game Logic service is running. Waiting for shutdown signal..."); utils::signal_handler::wait_for_signal().await; info!("Shutdown signal received. Beginning graceful shutdown..."); // Step 1: Signal the game logic task to stop if let Err(_) = game_logic_shutdown_tx.send(()) { warn!("Failed to send shutdown signal to game logic task (receiver may have been dropped)"); } // Step 2: Signal the gRPC server to stop accepting new connections if let Err(_) = shutdown_tx.send(()) { warn!("Failed to send shutdown signal to gRPC server (receiver may have been dropped)"); } // Step 3: Wait for both tasks to finish with timeouts let mut shutdown_errors = Vec::new(); match timeout(Duration::from_secs(10), game_logic_task).await { Ok(result) => { if let Err(e) = result { error!("Game logic task failed: {}", e); shutdown_errors.push(format!("Game logic task: {}", e)); } } Err(_) => { error!("Game logic task shutdown timed out after 10 seconds"); shutdown_errors.push("Game logic task: timeout".to_string()); } } match timeout(Duration::from_secs(30), server_task).await { Ok(result) => { if let Err(e) = result { error!("gRPC server task failed: {}", e); shutdown_errors.push(format!("gRPC server task: {}", e)); } } Err(_) => { error!("gRPC server shutdown timed out after 30 seconds"); shutdown_errors.push("gRPC server task: timeout".to_string()); } } if shutdown_errors.is_empty() { info!("All components shut down successfully"); } else { warn!("Some components failed to shut down cleanly: {:?}", shutdown_errors); } Ok(()) }