import asyncio from contextlib import asynccontextmanager from fastapi import FastAPI from sqlalchemy import select from app.config import settings from app.database import Base, async_session, engine from app.models import GameServer, Mode, ServerStatus from app.queue_manager import queue_manager from app.routers import matches, matchmaking, ranks, servers, stats from app.routers.servers import sweep_stale_servers # Three fake rows so server_select.gd has a realistic-looking list before a # real server pool exists (see README's "Not set up yet"). Only the first # port matches DEV_SEED_SERVER_IP/PORT, so it's the only one of the three # that's actually reachable by running a real Godot server locally — the # other two are display-only until real servers self-register on those # ports. player_count here is just the DB fallback; a live server overrides # it via the UDP query responder the client probes directly (see # spacewar/autoload/network_manager.gd). _DEMO_PLAYER_COUNTS = [40, 20, 10] async def _seed_demo_servers() -> None: async with async_session() as db: for i, player_count in enumerate(_DEMO_PLAYER_COUNTS): port = settings.dev_seed_server_port + i existing = ( await db.execute( select(GameServer).where( GameServer.ip == settings.dev_seed_server_ip, GameServer.port == port, GameServer.mode == Mode.casual, ) ) ).scalars().first() if existing is None: db.add( GameServer( ip=settings.dev_seed_server_ip, port=port, mode=Mode.casual, status=ServerStatus.available, player_count=player_count, max_players=50, ) ) await db.commit() async def _run_stale_sweep_loop() -> None: while True: await asyncio.sleep(5) async with async_session() as db: await sweep_stale_servers(db) @asynccontextmanager async def lifespan(app: FastAPI): async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) await _seed_demo_servers() matching_task = asyncio.create_task(queue_manager.run_matching_loop()) sweep_task = asyncio.create_task(_run_stale_sweep_loop()) yield matching_task.cancel() sweep_task.cancel() app = FastAPI(title="Spacewar Matchmaking API", lifespan=lifespan) app.include_router(matchmaking.router) app.include_router(matches.router) app.include_router(stats.router) app.include_router(ranks.router) app.include_router(servers.router) @app.get("/health") async def health() -> dict: return {"status": "ok"}