Set up simple chat server

This commit is contained in:
Paul Brinkmeier 2023-01-15 16:09:50 +01:00
parent 573fac40cc
commit 28e93b1fa3
4 changed files with 250 additions and 24 deletions

View File

@ -1,7 +1,39 @@
<script setup lang="ts">
import HelloWorld from './components/HelloWorld.vue'
<script lang="ts">
import Game from './components/Game.vue'
/*
Basically just opens the websocket connection to the server,
waits for the 'init' message and passes the initial state to the Game
component. Once the initial state has been received, no more event are processed.
*/
export default {
components: {
Game
},
data() {
return {
ws: new WebSocket('ws://localhost:5000/glebby'),
model: null
}
},
created() {
const messageListener = (ev: MessageEvent) => {
const message = JSON.parse(ev.data)
const payload = message.payload
if (payload.type === 'init') {
console.log('Received initial game state:')
console.log(payload.state)
this.model = payload.state
this.ws.removeEventListener('message', messageListener)
}
}
this.ws.addEventListener('message', messageListener)
}
}
</script>
<template>
<HelloWorld msg="Vite + Vue" />
<Game v-if="model" :ws="ws" :model="model" />
</template>

View File

@ -0,0 +1,102 @@
<script lang="ts">
import { defineComponent, PropType } from 'vue';
interface Player {
id: number,
name: string
}
interface Model {
yourId: number,
players: Player[]
}
function getPlayer(model: Model, id: number): Player | undefined {
return model.players.find(player => player.id === id)
}
/*
Handles all game business. Sends (local) player input to the server for distribution.
Receives player input from the server and manages state accordingly.
*/
export default defineComponent({
props: {
ws: { type: WebSocket, required: true },
model: { type: Object as PropType<Model>, required: true }
},
data() {
return {
chatMessages: [] as string[],
playerName: '',
chatMessage: ''
}
},
created() {
this.ws.addEventListener('message', (ev) => {
const message = JSON.parse(ev.data)
const payload = message.payload
console.log(`Message from player ${message.from}:`)
console.log(payload)
switch (payload.type) {
case 'join':
this.model.players.push({
id: message.from,
name: ''
})
break
case 'leave':
this.model.players = this.model.players.filter(player => player.id !== message.from)
break
case 'chat':
this.chatMessages.push(payload.message)
break
case 'set-name':
getPlayer(this.model, message.from)!.name = payload.name
break
}
})
},
methods: {
setName() {
this.ws.send(JSON.stringify({
type: 'set-name',
name: this.playerName
}))
},
sendChat() {
if (this.chatMessage === '') {
return
}
this.ws.send(JSON.stringify({
type: 'chat',
message: this.chatMessage
}))
this.chatMessage = ''
}
}
})
</script>
<template>
<pre>{{ JSON.stringify(model, null, 2) }}</pre>
<h1>I am #{{ model.yourId }}</h1>
<form @submit.prevent="setName">
<input v-model="playerName" placeholder="enter your name...">
<button >set name</button>
</form>
<form @submit.prevent="sendChat">
<input v-model="chatMessage">
<button>Post</button>
</form>
<h2>players</h2>
<ul v-if="model">
<li v-for="player in model.players" :key="player.id">{{ player.name }}#{{ player.id }}</li>
</ul>
<h2>chat</h2>
<ul v-if="model">
<li v-for="message in chatMessages">{{ message }}</li>
</ul>
</template>

View File

@ -1,15 +0,0 @@
<script setup lang="ts">
import { ref } from 'vue'
defineProps<{ msg: string }>()
const count = ref(0)
</script>
<template>
<h1>{{ msg }}</h1>
<div class="card">
<button type="button" @click="count++">count is {{ count }}</button>
</div>
</template>

View File

@ -1,16 +1,123 @@
from flask import Flask, render_template
from flask_sock import Sock
import json
import simple_websocket
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()
self.clients = dict()
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('/echo')
@sock.route('/glebby')
def echo(sock):
print('New echo client')
client_id = state.add_client(sock)
try:
while True:
data = sock.receive()
sock.send(data)
state.put_incoming_message(client_id, data)
except simple_websocket.ConnectionClosed:
print("Client closed the connection")
state.remove_client(client_id)