Foundations for pushing notifications to WebSocket clients and receiving commands from them

Search for a command to run...

No comments yet. Be the first to comment.
Hello. I'm writing a Lambda "tick" function to simulate the universe for my game. It's a different architecture than my previous idea of maintaining a 24/7 server, which simulates a busy real-time game loop. I wanted to give it a try. In this post, I...

A small tool that lets you put a "marker" on a song and have a button to recall that position with a click.

Hey there! I had a blast participating in the Appwrite hackathon and I want to share my journey with you. So, buckle up and get ready for some timer magic! Team Details Okay, before we dive in, let's get to know the team (well, it's just me actually)...

Hello. I've decided to try something for the game I'm developing. The game is a long-running online solo/coop space strategy game with resource balancing and 4x elements, but that's not the subject of this post. I decided to separate game logic code ...

Hello. For this one, I ended up using Hammer.js. I did try Interact.js which is definitely newer and more updated, but that just... well... didn't work nicely. Judging by the examples on both libraries' pages you can see if the core functionality of ...

Hello there. Today I will write about how I lay down the foundations for async communications between the clients and the server using WebSockets for my online persistent-universe single-player/co-op space game. I will also write a bit about my next steps which include wiring up all the things needed for handling user actions and processes in the game.
Most WebSocket tutorials end up in a sequential send/recv loop, but in my game, where I need to push messages asynchronously such as "construction complete" or "new stars were discovered" or a dreadful "space dragons are inbound in 25 hours" without waiting for an input from the WebSocket client, a slightly different tactic should be used.
The basic concept, taken straight from my WebSocket route handling class looks like this:
# start_loop is called after successful authentication
async def start_loop(self):
sub = self.auth_data["sub"]
try:
listener = self.listener(self.websocket)
poster = self.poster(self.websocket)
await gather(listener, poster) # from the asyncio module
except WebSocketDisconnect:
logger.info(f"{sub} disconnected, bye")
self.disconnect()
The listener coroutine looks like the following. Receive from the socket, then handle the command (not implemented yet).
async def listener(self, websocket: WebSocket):
sub = self.auth_data["sub"]
while True:
try:
data = await websocket.receive_json()
command = data["command"]
except JSONDecodeError:
await self.websocket.send_text("JSON. JSON please")
logger.warning(f"{sub} sent a bad JSON in request, very suspicious!")
continue
except KeyError:
command = None
await self.websocket.send_json(
ok_response(None, "thank you", command=command)
)
And the poster, with a POC for posting to the WebSocket asynchronously every 5 seconds:
async def poster(self, websocket: WebSocket):
while True:
await sleep(5.0) # TODO: await an Event from some Events Queue
await websocket.send_json(
ok_response(MessageType.Unknown, "Jamaica. Let's go to Jamaica.")
)
And in the browser, we can see pushes and commands sent independently:

Nice!
As for my next steps, I plan to implement the following logic as I described in a previous post (details below):

As I receive a WebSocket command, say... "Build Something", the Web Process will create a Celery task for that. A Worker Process will catch the task and create a DB entry in some Build Table for later reference and also create a delayed task ETA-ed for the time it would be completed. When the delayed task is due, a Worker will catch that and will check if the task is still valid in the Build Table (e.g. has not been cancelled). If valid, it will publish to a PubSub saying that the Build Something is complete. The Web Worker which is subscribed to that PubSub will receive the message and send a notification to all connected clients.
Phew.
I hope that will work.
Some thoughts about game restarts/crashes pop up now, especially for delayed tasks. I tend to cancel all tasks when a worker dies rather than relying on Redis to be persistent enough for me. When a game starts, I can read the Build Table and set up delayed tasks again. I think that would work better.
That would be all for now. I'll keep on updating.