Compare commits
No commits in common. "main" and "multiplayer-test" have entirely different histories.
main
...
multiplaye
|
@ -1,9 +1,12 @@
|
||||||
# CW Generator
|
# CW Generator
|
||||||
A simple application for generating self-made morse / CW audio files. All it features is a button as
|
A simple application for generating self-made morse / CW audio files. All it features is a button as
|
||||||
a morse key and allows you to export this as a WAV. Optionally, a MIDI keyboard can be used as a
|
a morse key and allows you to export this as a WAV. Optionally, a MIDI keyboard can be used as a
|
||||||
morse key (since godot 4.4 even on web).
|
morse key.
|
||||||
|
|
||||||
## Known Limitations / TODO
|
## Known Limitations / TODO
|
||||||
|
MIDI support only works locally for now.
|
||||||
|
* webmidi might be an option: https://gist.github.com/srejv/b7198e25587e2d8e0a66860781b56852
|
||||||
|
|
||||||
Currently we can only export wav, as Godot itself doesn't include any encoders. Having the ability
|
Currently we can only export wav, as Godot itself doesn't include any encoders. Having the ability
|
||||||
to export as mp3, ogg or opus would be nice, but external libraries are kind of a hassle. opus would
|
to export as mp3, ogg or opus would be nice, but external libraries are kind of a hassle. opus would
|
||||||
be the nicest format, but doesn't seem to work with older Iphones.
|
be the nicest format, but doesn't seem to work with older Iphones.
|
||||||
|
|
|
@ -1,20 +1,18 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import datetime
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from websockets.asyncio.connection import broadcast
|
|
||||||
from websockets.asyncio.server import serve
|
from websockets.asyncio.server import serve
|
||||||
|
|
||||||
__VERSION__ = "0.0.1"
|
__VERSION__ = "0.0.1"
|
||||||
|
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
level=logging.INFO,
|
level=logging.INFO,
|
||||||
format="%(asctime)s %(levelname)s %(message)s"
|
format="%(asctime)s %(name)s %(levelname)s %(message)s"
|
||||||
)
|
)
|
||||||
|
|
||||||
LOG = logging.getLogger()
|
|
||||||
|
|
||||||
|
|
||||||
class Client:
|
class Client:
|
||||||
freqs = {}
|
freqs = {}
|
||||||
|
@ -25,7 +23,7 @@ class Client:
|
||||||
self.curr_freq = None
|
self.curr_freq = None
|
||||||
|
|
||||||
async def handle(self):
|
async def handle(self):
|
||||||
LOG.info(">>> New client %s connected", self.client)
|
print(f" >>> New client {self.client} connected")
|
||||||
exc = None
|
exc = None
|
||||||
try:
|
try:
|
||||||
await self._handle_client()
|
await self._handle_client()
|
||||||
|
@ -33,7 +31,7 @@ class Client:
|
||||||
exc = e
|
exc = e
|
||||||
finally:
|
finally:
|
||||||
# FIXME: basically handle disconnect / leave from room
|
# FIXME: basically handle disconnect / leave from room
|
||||||
LOG.info("<<< Client %s id %s disconnected: %s", self.client, self.id, exc)
|
print(f" <<< Client {self.client} id {self.id} disconnected: {exc}")
|
||||||
if self.curr_freq:
|
if self.curr_freq:
|
||||||
await self._leave_room()
|
await self._leave_room()
|
||||||
|
|
||||||
|
@ -51,19 +49,18 @@ class Client:
|
||||||
async def _handle_client(self):
|
async def _handle_client(self):
|
||||||
await self._send(type="hello", name="LobbySrv 3000", version=__VERSION__)
|
await self._send(type="hello", name="LobbySrv 3000", version=__VERSION__)
|
||||||
async for data in self.websocket:
|
async for data in self.websocket:
|
||||||
|
print(f" <-- client {self.client} sent {repr(data)}")
|
||||||
try:
|
try:
|
||||||
data = json.loads(data)
|
data = json.loads(data)
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
self._send_error("Could not decode message, invalid json")
|
self._send_error("Could not decode message, invalid json")
|
||||||
LOG.error("client %s sent broken data %s", self.client, repr(data))
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not isinstance(data, dict) or "cmd" not in data:
|
if not isinstance(data, dict) or "cmd" not in data:
|
||||||
await self._send_error("Invalid format in json")
|
await self._send_error("Invalid format in json")
|
||||||
LOG.error("client %s sent broken data (no cmd key in data) %s", self.client, repr(data))
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
LOG.info("client %s wrote: %s", self.client, data)
|
print(f"{datetime.datetime.now()} {self.client} wrote:", data)
|
||||||
|
|
||||||
match data["cmd"]:
|
match data["cmd"]:
|
||||||
case "quit":
|
case "quit":
|
||||||
|
@ -127,7 +124,7 @@ class Client:
|
||||||
self.curr_freq = freq
|
self.curr_freq = freq
|
||||||
self.freqs[freq].append(self)
|
self.freqs[freq].append(self)
|
||||||
# FIXME: do we need locking here?
|
# FIXME: do we need locking here?
|
||||||
LOG.debug("FREQ %s %s %s", self.curr_freq, freq, self.freqs)
|
print("FREQ", self.curr_freq, freq, self.freqs)
|
||||||
await self._send(type="join", freq=self.curr_freq, self_id=self.id,
|
await self._send(type="join", freq=self.curr_freq, self_id=self.id,
|
||||||
other_players=[c.id for c in self._others(freq)])
|
other_players=[c.id for c in self._others(freq)])
|
||||||
await self._send_to_group(self._others(freq), type="player-joined", player=self.id)
|
await self._send_to_group(self._others(freq), type="player-joined", player=self.id)
|
||||||
|
@ -145,18 +142,19 @@ class Client:
|
||||||
type="morse-state", state=data["state"], from_player=self.id)
|
type="morse-state", state=data["state"], from_player=self.id)
|
||||||
|
|
||||||
async def _leave_room(self):
|
async def _leave_room(self):
|
||||||
if self.curr_freq:
|
if not self.curr_freq:
|
||||||
await self._send_to_group(self._others(self.curr_freq),
|
self._send_error("You are not on a frequency")
|
||||||
type="player-left", player=self.id)
|
return
|
||||||
try:
|
|
||||||
self.freqs[self.curr_freq].remove(self)
|
await self._send_to_group(self._others(self.curr_freq),
|
||||||
except ValueError:
|
type="player-left", player=self.id)
|
||||||
LOG.warning("Player %s was not in freq %s", self.id, self.curr_freq)
|
try:
|
||||||
if not self.freqs[self.curr_freq]:
|
self.freqs[self.curr_freq].remove(self)
|
||||||
del self.freqs[self.curr_freq]
|
except ValueError:
|
||||||
self.curr_freq = None
|
print(f"Warning: Player {self.id} was not in freq {self.curr_freq}")
|
||||||
else:
|
if not self.freqs[self.curr_freq]:
|
||||||
LOG.warning("Client %s is not on a frequency, sending a 'leave' nontheless", self.client)
|
del self.freqs[self.curr_freq]
|
||||||
|
self.curr_freq = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await self._send(type="leave")
|
await self._send(type="leave")
|
||||||
|
@ -168,17 +166,18 @@ class Client:
|
||||||
|
|
||||||
async def _send(self, ignore_exceptions=False, **kwargs):
|
async def _send(self, ignore_exceptions=False, **kwargs):
|
||||||
data = json.dumps(kwargs).encode()
|
data = json.dumps(kwargs).encode()
|
||||||
LOG.debug("--> sending out to %s: %s", self.client, data)
|
print(f" --> sending out to {self.client}: {data}")
|
||||||
try:
|
try:
|
||||||
await self.websocket.send(json.dumps(kwargs).encode() + b"\n")
|
await self.websocket.send(json.dumps(kwargs).encode() + b"\n")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error("Error sending data to %s: %s", self.client, e)
|
print(f"Error sending data to {self.client}: {e}")
|
||||||
if not ignore_exceptions:
|
if not ignore_exceptions:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
async def _send_to_group(self, group, **kwargs):
|
async def _send_to_group(self, group, **kwargs):
|
||||||
LOG.info("broadcast() to %s clients: %s", len(group), kwargs)
|
async with asyncio.TaskGroup() as tg:
|
||||||
broadcast([c.websocket for c in group], json.dumps(kwargs).encode() + b"\n")
|
for member in group:
|
||||||
|
tg.create_task(member._send(ignore_exceptions=True, **kwargs))
|
||||||
|
|
||||||
async def _send_error(self, msg: str):
|
async def _send_error(self, msg: str):
|
||||||
await self._send(type="error", message=msg)
|
await self._send(type="error", message=msg)
|
||||||
|
@ -201,5 +200,5 @@ async def main():
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
LOG.info("Starting server")
|
print("Starting server")
|
||||||
asyncio.run(main())
|
asyncio.run(main())
|
||||||
|
|
Loading…
Reference in New Issue