Some checks failed
Release / release (push) Has been cancelled
Full-stack dashboard for controlling, automating, and analyzing Artifacts MMO characters via the game's HTTP API. Backend (FastAPI): - Async Artifacts API client with rate limiting and retry - 6 automation strategies (combat, gathering, crafting, trading, task, leveling) - Automation engine with runner, manager, cooldown tracker, pathfinder - WebSocket relay (game server -> frontend) - Game data cache, character snapshots, price history, analytics - 9 API routers, 7 database tables, 3 Alembic migrations - 108 unit tests Frontend (Next.js 15 + shadcn/ui): - Live character dashboard with HP/XP bars and cooldowns - Character detail with stats, equipment, inventory, skills, manual actions - Automation management with live log streaming - Interactive canvas map with content-type coloring and zoom/pan - Bank management, Grand Exchange with price charts - Events, logs, analytics pages with Recharts - WebSocket auto-reconnect with query cache invalidation - Settings page, error boundaries, dark theme Infrastructure: - Docker Compose (dev + prod) - GitHub Actions CI/CD - Documentation (Architecture, Automation, Deployment, API)
80 lines
2.7 KiB
Python
80 lines
2.7 KiB
Python
"""Event handlers for processing game events from the WebSocket.
|
|
|
|
The :class:`GameEventHandler` subscribes to all events on the bus and
|
|
can be extended with domain-specific logic (e.g. updating caches,
|
|
triggering automation adjustments, etc.).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import logging
|
|
|
|
from app.websocket.event_bus import EventBus
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class GameEventHandler:
|
|
"""Process game events received via the EventBus."""
|
|
|
|
def __init__(self, event_bus: EventBus) -> None:
|
|
self._event_bus = event_bus
|
|
self._queue: asyncio.Queue | None = None
|
|
self._task: asyncio.Task | None = None
|
|
|
|
# ------------------------------------------------------------------
|
|
# Lifecycle
|
|
# ------------------------------------------------------------------
|
|
|
|
async def start(self) -> asyncio.Task:
|
|
"""Subscribe to all events and start the processing loop."""
|
|
self._queue = self._event_bus.subscribe_all()
|
|
self._task = asyncio.create_task(
|
|
self._process_loop(),
|
|
name="game-event-handler",
|
|
)
|
|
logger.info("Game event handler started")
|
|
return self._task
|
|
|
|
async def stop(self) -> None:
|
|
"""Stop the processing loop and unsubscribe."""
|
|
if self._task is not None:
|
|
self._task.cancel()
|
|
try:
|
|
await self._task
|
|
except asyncio.CancelledError:
|
|
pass
|
|
if self._queue is not None:
|
|
self._event_bus.unsubscribe("*", self._queue)
|
|
logger.info("Game event handler stopped")
|
|
|
|
# ------------------------------------------------------------------
|
|
# Processing
|
|
# ------------------------------------------------------------------
|
|
|
|
async def _process_loop(self) -> None:
|
|
"""Read events from the queue and dispatch to handlers."""
|
|
assert self._queue is not None
|
|
try:
|
|
while True:
|
|
event = await self._queue.get()
|
|
try:
|
|
await self._handle(event)
|
|
except Exception:
|
|
logger.exception(
|
|
"Error handling event: %s", event.get("type")
|
|
)
|
|
except asyncio.CancelledError:
|
|
logger.debug("Event handler process loop cancelled")
|
|
|
|
async def _handle(self, event: dict) -> None:
|
|
"""Handle a single event.
|
|
|
|
Override or extend this method to add domain-specific logic.
|
|
Currently logs notable game events for observability.
|
|
"""
|
|
event_type = event.get("type", "")
|
|
|
|
if event_type.startswith("game_"):
|
|
logger.info("Game event: %s", event_type)
|