glebby/glebby-server/glebby.py

127 lines
3.5 KiB
Python

import json
import simple_websocket
from collections import OrderedDict
from flask import Flask
from flask_sock import Sock
from queue import Queue
from threading import Thread, Lock
class Client:
def __init__(self, sock, client_id):
self.sock = sock
self.data = { 'id': client_id, 'name': '' }
class GlebbyState:
def __init__(self):
self.clients_lock = Lock()
# We want to preserve the order that clients arrived in,
# e.g. for whose turn it is
self.clients = OrderedDict()
self.next_client_id_lock = Lock()
self.next_client_id = 0
self.incoming_messages = Queue()
# domain stuff
def handle_message_from(self, client_id, payload):
print(f"{client_id}: {payload}")
if payload['type'] == 'set-name':
self.clients[client_id].data['name'] = payload['name']
self.broadcast(client_id, {
'type': 'set-name',
'name': payload['name']
})
elif payload['type'] == 'chat':
self.broadcast(client_id, {
'type': 'chat',
'message': payload['message']
})
else:
print("Unhandled!")
def get_state_dto(self, client_id):
return {
'yourId': client_id,
'players': [
self.clients[other_client_id].data
for other_client_id in self.clients
]
}
# message receiving and sending
def put_incoming_message(self, client_id, payload):
self.incoming_messages.put({
'from': client_id,
'payload': json.loads(payload)
})
def send_to(self, client_id_from, client_id_to, payload):
self.clients[client_id_to].sock.send(json.dumps({
'from': client_id_from,
'payload': payload
}))
def broadcast(self, client_id_from, payload):
with self.clients_lock:
for client_id_to in self.clients:
self.send_to(client_id_from, client_id_to, payload)
# client management
def _get_next_client_id(self):
with self.next_client_id_lock:
client_id = self.next_client_id
self.next_client_id += 1
return client_id
def add_client(self, sock):
client_id = self._get_next_client_id()
self.broadcast(client_id, {'type': 'join'})
with self.clients_lock:
self.clients[client_id] = Client(sock, client_id)
self.send_to(None, client_id, {
'type': 'init',
'state': self.get_state_dto(client_id)
})
return client_id
def remove_client(self, client_id):
with self.clients_lock:
del self.clients[client_id]
self.broadcast(client_id, {'type': 'leave'})
# event thread stuff
def start_event_thread(self):
event_thread = Thread(target=lambda: self._event_thread())
event_thread.daemon = True
event_thread.start()
def _event_thread(self):
while True:
msg = self.incoming_messages.get()
self.handle_message_from(msg['from'], msg['payload'])
# Initialization
state = GlebbyState()
state.start_event_thread()
app = Flask(__name__)
sock = Sock(app)
@sock.route('/glebby')
def echo(sock):
client_id = state.add_client(sock)
try:
while True:
data = sock.receive()
state.put_incoming_message(client_id, data)
except simple_websocket.ConnectionClosed:
state.remove_client(client_id)