2024-05-04 04:18:21 +00:00
|
|
|
import asyncio
|
2024-05-04 20:35:42 +00:00
|
|
|
from collections import deque
|
2024-05-05 14:14:54 +00:00
|
|
|
from json import dumps, loads
|
2024-05-04 04:18:21 +00:00
|
|
|
from logging import getLogger
|
|
|
|
from threading import Thread
|
2024-05-08 01:42:10 +00:00
|
|
|
from typing import Literal
|
2024-05-05 18:54:39 +00:00
|
|
|
from uuid import uuid4
|
2024-05-04 04:18:21 +00:00
|
|
|
|
|
|
|
import websockets
|
|
|
|
|
2024-05-09 02:11:16 +00:00
|
|
|
from adventure.context import get_actor_agent_for_name, set_actor_agent
|
|
|
|
from adventure.models.entity import Actor, Room, World
|
|
|
|
from adventure.models.event import (
|
|
|
|
ActionEvent,
|
|
|
|
GameEvent,
|
|
|
|
GenerateEvent,
|
|
|
|
PromptEvent,
|
|
|
|
ReplyEvent,
|
|
|
|
ResultEvent,
|
|
|
|
StatusEvent,
|
|
|
|
)
|
2024-05-08 01:42:10 +00:00
|
|
|
from adventure.player import (
|
|
|
|
RemotePlayer,
|
|
|
|
get_player,
|
|
|
|
has_player,
|
|
|
|
list_players,
|
|
|
|
remove_player,
|
|
|
|
set_player,
|
|
|
|
)
|
2024-05-04 04:18:21 +00:00
|
|
|
from adventure.state import snapshot_world, world_json
|
|
|
|
|
|
|
|
logger = getLogger(__name__)
|
|
|
|
|
|
|
|
connected = set()
|
2024-05-05 14:14:54 +00:00
|
|
|
recent_events = deque(maxlen=100)
|
2024-05-09 02:11:16 +00:00
|
|
|
last_snapshot = None
|
2024-05-04 04:18:21 +00:00
|
|
|
|
|
|
|
|
|
|
|
async def handler(websocket):
|
2024-05-05 18:54:39 +00:00
|
|
|
id = uuid4().hex
|
|
|
|
logger.info("Client connected, given id: %s", id)
|
2024-05-04 04:18:21 +00:00
|
|
|
connected.add(websocket)
|
2024-05-04 20:35:42 +00:00
|
|
|
|
2024-05-05 14:14:54 +00:00
|
|
|
async def next_turn(character: str, prompt: str) -> None:
|
2024-05-05 18:54:39 +00:00
|
|
|
await websocket.send(
|
|
|
|
dumps(
|
|
|
|
{
|
|
|
|
"type": "prompt",
|
|
|
|
"id": id,
|
|
|
|
"character": character,
|
|
|
|
"prompt": prompt,
|
|
|
|
"actions": [],
|
|
|
|
}
|
|
|
|
),
|
|
|
|
)
|
2024-05-05 14:14:54 +00:00
|
|
|
|
2024-05-09 02:11:16 +00:00
|
|
|
def sync_turn(event: PromptEvent) -> bool:
|
2024-05-08 01:42:10 +00:00
|
|
|
player = get_player(id)
|
2024-05-09 02:11:16 +00:00
|
|
|
if player and player.name == event.actor.name:
|
|
|
|
asyncio.run(next_turn(event.actor.name, event.prompt))
|
2024-05-08 01:42:10 +00:00
|
|
|
return True
|
2024-05-05 14:14:54 +00:00
|
|
|
|
2024-05-08 01:42:10 +00:00
|
|
|
return False
|
2024-05-05 14:14:54 +00:00
|
|
|
|
2024-05-04 20:35:42 +00:00
|
|
|
try:
|
2024-05-05 18:54:39 +00:00
|
|
|
await websocket.send(dumps({"type": "id", "id": id}))
|
|
|
|
|
2024-05-09 02:11:16 +00:00
|
|
|
if last_snapshot:
|
|
|
|
await websocket.send(last_snapshot)
|
2024-05-04 20:35:42 +00:00
|
|
|
|
|
|
|
for message in recent_events:
|
|
|
|
await websocket.send(message)
|
|
|
|
except Exception:
|
|
|
|
logger.exception("Failed to send recent messages to new client")
|
|
|
|
|
2024-05-04 04:18:21 +00:00
|
|
|
while True:
|
|
|
|
try:
|
2024-05-05 14:14:54 +00:00
|
|
|
# if this socket is attached to a character and that character's turn is active, wait for input
|
2024-05-04 04:18:21 +00:00
|
|
|
message = await websocket.recv()
|
2024-05-05 19:11:41 +00:00
|
|
|
logger.info(f"Received message for {id}: {message}")
|
2024-05-05 14:14:54 +00:00
|
|
|
|
|
|
|
try:
|
|
|
|
data = loads(message)
|
2024-05-05 18:54:39 +00:00
|
|
|
message_type = data.get("type", None)
|
|
|
|
if message_type == "player":
|
2024-05-09 02:11:16 +00:00
|
|
|
character_name = data["become"]
|
|
|
|
if has_player(character_name):
|
|
|
|
logger.error(f"Character {character_name} is already in use")
|
|
|
|
continue
|
|
|
|
|
2024-05-08 01:42:10 +00:00
|
|
|
# TODO: should this always remove?
|
|
|
|
remove_player(id)
|
2024-05-05 14:14:54 +00:00
|
|
|
|
2024-05-05 18:54:39 +00:00
|
|
|
actor, llm_agent = get_actor_agent_for_name(character_name)
|
2024-05-05 14:14:54 +00:00
|
|
|
if not actor:
|
|
|
|
logger.error(f"Failed to find actor {character_name}")
|
|
|
|
continue
|
|
|
|
|
2024-05-05 22:46:24 +00:00
|
|
|
# prevent any recursive fallback bugs
|
|
|
|
if isinstance(llm_agent, RemotePlayer):
|
|
|
|
logger.warning(
|
|
|
|
"patching recursive fallback for %s", character_name
|
|
|
|
)
|
|
|
|
llm_agent = llm_agent.fallback_agent
|
|
|
|
|
2024-05-05 18:54:39 +00:00
|
|
|
# player_name = data["player"]
|
2024-05-05 22:46:24 +00:00
|
|
|
player = RemotePlayer(
|
|
|
|
actor.name, actor.backstory, sync_turn, fallback_agent=llm_agent
|
|
|
|
)
|
2024-05-08 01:42:10 +00:00
|
|
|
set_player(id, player)
|
2024-05-05 20:50:38 +00:00
|
|
|
logger.info(f"Client {id} is now character {character_name}")
|
2024-05-05 18:54:39 +00:00
|
|
|
|
|
|
|
# swap out the LLM agent
|
2024-05-09 02:11:16 +00:00
|
|
|
set_actor_agent(actor.name, actor, player)
|
2024-05-05 18:54:39 +00:00
|
|
|
|
|
|
|
# notify all clients that this character is now active
|
2024-05-06 01:17:00 +00:00
|
|
|
player_event(character_name, id, "join")
|
|
|
|
player_list()
|
2024-05-08 01:42:10 +00:00
|
|
|
elif message_type == "input":
|
|
|
|
player = get_player(id)
|
|
|
|
if player and isinstance(player, RemotePlayer):
|
|
|
|
logger.info(
|
|
|
|
"queueing input for player %s: %s", player.name, data
|
|
|
|
)
|
|
|
|
player.input_queue.put(data["input"])
|
2024-05-05 14:14:54 +00:00
|
|
|
|
|
|
|
except Exception:
|
|
|
|
logger.exception("Failed to parse message")
|
2024-05-04 04:18:21 +00:00
|
|
|
except websockets.ConnectionClosedOK:
|
|
|
|
break
|
|
|
|
|
|
|
|
connected.remove(websocket)
|
2024-05-05 14:14:54 +00:00
|
|
|
|
2024-05-05 18:54:39 +00:00
|
|
|
# swap out the character for the original agent when they disconnect
|
2024-05-08 01:42:10 +00:00
|
|
|
player = get_player(id)
|
|
|
|
if player and isinstance(player, RemotePlayer):
|
|
|
|
remove_player(id)
|
2024-05-05 18:54:39 +00:00
|
|
|
|
2024-05-06 01:17:00 +00:00
|
|
|
logger.info("Disconnecting player for %s", player.name)
|
|
|
|
player_event(player.name, id, "leave")
|
|
|
|
player_list()
|
|
|
|
|
2024-05-05 18:54:39 +00:00
|
|
|
actor, _ = get_actor_agent_for_name(player.name)
|
2024-05-06 01:17:00 +00:00
|
|
|
if actor and player.fallback_agent:
|
|
|
|
logger.info("Restoring LLM agent for %s", player.name)
|
2024-05-09 02:11:16 +00:00
|
|
|
set_actor_agent(player.name, actor, player.fallback_agent)
|
2024-05-05 14:14:54 +00:00
|
|
|
|
2024-05-06 01:17:00 +00:00
|
|
|
logger.info("Client disconnected: %s", id)
|
2024-05-04 04:18:21 +00:00
|
|
|
|
|
|
|
|
|
|
|
socket_thread = None
|
|
|
|
static_thread = None
|
|
|
|
|
|
|
|
|
2024-05-04 20:35:42 +00:00
|
|
|
def server_json(obj):
|
|
|
|
if isinstance(obj, Actor):
|
|
|
|
return obj.name
|
|
|
|
|
|
|
|
if isinstance(obj, Room):
|
|
|
|
return obj.name
|
|
|
|
|
|
|
|
return world_json(obj)
|
|
|
|
|
|
|
|
|
|
|
|
def send_and_append(message):
|
|
|
|
json_message = dumps(message, default=server_json)
|
|
|
|
recent_events.append(json_message)
|
|
|
|
websockets.broadcast(connected, json_message)
|
|
|
|
return json_message
|
|
|
|
|
|
|
|
|
2024-05-04 04:18:21 +00:00
|
|
|
def launch_server():
|
|
|
|
global socket_thread, static_thread
|
|
|
|
|
|
|
|
def run_sockets():
|
|
|
|
asyncio.run(server_main())
|
|
|
|
|
2024-05-09 02:11:16 +00:00
|
|
|
socket_thread = Thread(target=run_sockets, daemon=True)
|
2024-05-04 04:18:21 +00:00
|
|
|
socket_thread.start()
|
|
|
|
|
2024-05-09 02:11:16 +00:00
|
|
|
return [socket_thread]
|
|
|
|
|
2024-05-04 04:18:21 +00:00
|
|
|
|
|
|
|
async def server_main():
|
|
|
|
async with websockets.serve(handler, "", 8001):
|
|
|
|
logger.info("Server started")
|
|
|
|
await asyncio.Future() # run forever
|
|
|
|
|
|
|
|
|
|
|
|
def server_system(world: World, step: int):
|
2024-05-09 02:11:16 +00:00
|
|
|
global last_snapshot
|
2024-05-04 04:18:21 +00:00
|
|
|
json_state = {
|
|
|
|
**snapshot_world(world, step),
|
|
|
|
"type": "world",
|
|
|
|
}
|
2024-05-09 02:11:16 +00:00
|
|
|
last_snapshot = send_and_append(json_state)
|
2024-05-04 04:18:21 +00:00
|
|
|
|
|
|
|
|
|
|
|
def server_result(room: Room, actor: Actor, action: str):
|
|
|
|
json_action = {
|
2024-05-04 20:35:42 +00:00
|
|
|
"actor": actor,
|
2024-05-04 04:18:21 +00:00
|
|
|
"result": action,
|
2024-05-04 20:35:42 +00:00
|
|
|
"room": room,
|
2024-05-04 04:18:21 +00:00
|
|
|
"type": "result",
|
|
|
|
}
|
2024-05-04 20:35:42 +00:00
|
|
|
send_and_append(json_action)
|
2024-05-04 04:18:21 +00:00
|
|
|
|
|
|
|
|
2024-05-04 20:35:42 +00:00
|
|
|
def server_action(room: Room, actor: Actor, message: str):
|
2024-05-04 04:18:21 +00:00
|
|
|
json_input = {
|
2024-05-04 20:35:42 +00:00
|
|
|
"actor": actor,
|
2024-05-04 04:18:21 +00:00
|
|
|
"input": message,
|
2024-05-04 20:35:42 +00:00
|
|
|
"room": room,
|
|
|
|
"type": "action",
|
|
|
|
}
|
|
|
|
send_and_append(json_input)
|
|
|
|
|
|
|
|
|
2024-05-09 02:11:16 +00:00
|
|
|
def server_generate(event: GenerateEvent):
|
2024-05-04 20:35:42 +00:00
|
|
|
json_broadcast = {
|
2024-05-09 02:11:16 +00:00
|
|
|
"name": event.name,
|
|
|
|
"type": "generate",
|
2024-05-04 04:18:21 +00:00
|
|
|
}
|
2024-05-04 20:35:42 +00:00
|
|
|
send_and_append(json_broadcast)
|
2024-05-06 01:17:00 +00:00
|
|
|
|
|
|
|
|
2024-05-09 02:11:16 +00:00
|
|
|
def server_event(event: GameEvent):
|
|
|
|
if isinstance(event, GenerateEvent):
|
|
|
|
return server_generate(event)
|
|
|
|
elif isinstance(event, ActionEvent):
|
|
|
|
return server_action(event.room, event.actor, event.action)
|
|
|
|
elif isinstance(event, ReplyEvent):
|
|
|
|
return server_action(event.room, event.actor, event.text)
|
|
|
|
elif isinstance(event, ResultEvent):
|
|
|
|
return server_result(event.room, event.actor, event.result)
|
|
|
|
elif isinstance(event, StatusEvent):
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
logger.warning("Unknown event type: %s", event)
|
|
|
|
|
|
|
|
|
2024-05-06 01:17:00 +00:00
|
|
|
def player_event(character: str, id: str, event: Literal["join", "leave"]):
|
|
|
|
json_broadcast = {
|
|
|
|
"type": "player",
|
|
|
|
"character": character,
|
|
|
|
"id": id,
|
|
|
|
"event": event,
|
|
|
|
}
|
|
|
|
send_and_append(json_broadcast)
|
|
|
|
|
|
|
|
|
|
|
|
def player_list():
|
2024-05-08 01:42:10 +00:00
|
|
|
players = {value: key for key, value in list_players()}
|
|
|
|
json_broadcast = {
|
2024-05-06 01:17:00 +00:00
|
|
|
"type": "players",
|
2024-05-08 01:42:10 +00:00
|
|
|
"players": players,
|
2024-05-06 01:17:00 +00:00
|
|
|
}
|
|
|
|
send_and_append(json_broadcast)
|