1

I'm developing a text game in Python implementing the networking part using Quart and websockets. I'm done with the single player part which is a simple single request- single response thing and next I want to implement a PvP part. Reading the Quart documentation I almost figured how to broadcast messages to both players. The implementation is done using asyncio.Queue() for reasons I partially understand. However when I removed the whole queue logic, things went a bit strange. That is, after the second player/websocket connects he gets both his message and the other player's message and the first player gets nothing. So it seems that asyncio.Queue() does more under the hood than a simple queue but I really don't understand what or why.

Below is the server code, I have both implementations demonstrated for anyone willing to see for himself.

import asyncio
from functools import wraps

from quart import Quart, websocket

app = Quart(__name__)

connected = set()


def collect_websocket(func):
    @wraps(func)
    async def wrapper(*args, **kwargs):
        global connected
        connected.add(websocket)
        try:
            return await func(*args, **kwargs)
        finally:
            connected.remove(websocket)

    return wrapper


@app.websocket('/ws')
@collect_websocket
async def ws():
    await websocket.send("Welcome")
    await handler()
    while True:
        data = await websocket.receive()
        await websocket.send(data)


async def handler():
    if len(connected) > 1:
        for c in connected:
            await c.send("What do you do [a] [b] or [c]")


connected_websockets = set()


def collect_websocket_with_queue(func):
    @wraps(func)
    async def wrapper(*args, **kwargs):
        global connected_websockets
        queue = asyncio.Queue()
        connected_websockets.add(queue)
        try:
            return await func(queue, *args, **kwargs)
        finally:
            connected_websockets.remove(queue)

    return wrapper


@app.websocket('/queue')
@collect_websocket_with_queue
async def wsqueue(queue):
    await websocket.send("Welcome")
    await handlerQueue()
    while True:
        data = await queue.get()
        await websocket.send(data)


async def handlerQueue():
    if len(connected_websockets) > 1:
        for queue in connected_websockets:
            await queue.put("What do you do [a] [b] or [c]")


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000, debug=False)

Secondly, what I fail to achieve both with the queue and without is to make the code wait until both players send their input. What I tried was something like:

for c in connected:
    await c.receive()

or for the implementation with queues:

for queue in connected:
    await queue.get()

but none of these worked. How could that be achieved? Speaking with code:

async def handler():
    if len(connected) > 1:
        for c in connected:
            await c.send("What do you do [a] [b] or [c]")
        #############
        # Function or block of code for getting both players input
        ############
        for c in connected:
            await c.send("That and that happened, now do what?")
        #############
        # Function or block of code for getting both players input
        ############
        ## repeat until winning condition is met

Of course in the future I'll have to implement the client handling situation better but for the time being I want to see the flow happening to figure out some decisions.

I'm open to different implementations and maybe tools if anyone has to suggest.

Thanks for your time.

PS: The websocket client testing is done using Postman's websocket feature and practically each client is represented by a different tab.

Nick M
  • 69
  • 7
  • If would like to stick with asyncio, I suggest reading carefully how asyncio works and build a lifetime of your objects with some tool as draw.io to figure out where your object's lifetime is going sideways. `is done using asyncio.Queue() for reasons I partially understand` if you do not understand asyncio, rewrite the code without it. It should be more clear for you. – Evandro Coan Dec 31 '21 at 03:39
  • The code cannot be done without asyncio, that's for sure. What I don't really get is how the websocket and the queue are interchangeably used, how are they linked. I mean how from this piece of code `queue = asyncio.Queue() connected_websockets.add(queue)` you go to this one `await queue.get()` And really, here is the asyncio.Queue() [documentation](https://docs.python.org/3/library/asyncio-queue.html), I don't really see any explanation. – Nick M Dec 31 '21 at 17:45
  • The python standard documentation is not good to explain the concept of asyncio (coroutines) because it assumes you already know that. Start with these two: https://realpython.com/async-io-python/ and https://bbc.github.io/cloudfit-public-docs/asyncio/asyncio-part-1.html. There is also these others which should complement/reinforce the concepts you saw in the first two: https://www.datacamp.com/community/tutorials/asyncio-introduction, https://djangostars.com/blog/asynchronous-programming-in-python-asyncio/, https://pythonprogramming.net/asyncio-basics-intermediate-python-tutorial/. – Evandro Coan Dec 31 '21 at 23:35
  • The question looks like `pleaser, fix my code`. Is this code really a minimal example? (https://stackoverflow.com/help/minimal-reproducible-example). What is the question exactly? `after the second player/websocket connects he gets both his message and the other player's message and the first player gets nothing`? – Evandro Coan Dec 31 '21 at 23:41
  • @user well if you try it you'll see it runs, after installing quart package of course. the question is why it works with a queue and it does not without it. – Nick M Jan 01 '22 at 16:05

0 Answers0