From 375e9316f720deee57af4ee989b3d956510d9c3c Mon Sep 17 00:00:00 2001 From: tomoron Date: Sat, 14 Sep 2024 21:41:29 +0200 Subject: [PATCH] websocket consumer to async for game infinite loop. game requests now work like stated in comment --- .../djangoserver/file/server/server/Game.py | 79 ++++++++++----- .../server/typeRequests/createAccount.py | 11 ++- .../server/typeRequests/gameActions/leave.py | 14 +++ .../server/typeRequests/gameActions/ready.py | 6 +- .../server/typeRequests/gameActions/start.py | 9 +- .../server/server/typeRequests/gameRequest.py | 13 +-- .../server/typeRequests/getAllListUser.py | 8 +- .../typeRequests/getPrivateListMessage.py | 45 ++------- .../server/typeRequests/getPrivateListUser.py | 47 +-------- .../file/server/server/typeRequests/login.py | 8 +- .../server/typeRequests/sendPrivateMessage.py | 10 +- .../file/server/server/websocket.py | 98 ++++++++++++------- listError.txt | 1 + 13 files changed, 185 insertions(+), 164 deletions(-) create mode 100644 docker-compose/requirements/djangoserver/file/server/server/typeRequests/gameActions/leave.py diff --git a/docker-compose/requirements/djangoserver/file/server/server/Game.py b/docker-compose/requirements/djangoserver/file/server/server/Game.py index 4b0ea3f..09b7de7 100644 --- a/docker-compose/requirements/djangoserver/file/server/server/Game.py +++ b/docker-compose/requirements/djangoserver/file/server/server/Game.py @@ -6,37 +6,40 @@ # By: tomoron +#+ +:+ +#+ # # +#+#+#+#+#+ +#+ # # Created: 2024/09/13 16:20:58 by tomoron #+# #+# # -# Updated: 2024/09/14 12:01:35 by tomoron ### ########.fr # +# Updated: 2024/09/14 21:30:54 by tomoron ### ########.fr # # # # **************************************************************************** # import time import json +import asyncio class Game: waitingForPlayerLock = False waitingForPlayer = None def __init__(self, socket, withBot): - self.p1 = None; + self.p1 = None self.p2 = None self.p1Ready = False self.p2Ready = False self.bot = withBot - print("new game start") + self.started = False + self.playerLeft = False + self.end = False - if(not withBot): - print("no bot") + if(withBot): + self.join(socket) + else: while(Game.waitingForPlayerLock): time.sleep(0.05) Game.waitingForPlayerLock = True if(Game.waitingForPlayer == None): - print("no player waiting") Game.waitingForPlayer = self + socket.sync_send({"type":"game","content":{"action":0}}) self.join(socket) else: - print("player waiting") Game.waitingForPlayer.join(socket) - Game.waitingForPlayer = None; + Game.waitingForPlayer = None Game.waitingForPlayerLock = False def join(self, socket): @@ -46,31 +49,59 @@ class Game: else: self.p2 = socket socket.game = self - data = json.dumps({"type":"game", "content":{ - "action" : 0, - "id": socket.id, - "username": socket.username - }}) - self.p1.send(text_data=data) - if(self.p2 != None): - self.p2.send(text_data=data) + if(self.p2 != None and self.p1 != None): + data = json.dumps({"type":"game", "content":{ + "action" : 1, + "id": socket.id, + "username": socket.username + }}) + self.p1.sync_send(data) + self.p2.sync_send(data) except Exception as e: socket.sendError("invalid request", 9005, e) - def setReady(self, socket): - print("ready request") + async def setReady(self, socket): if(socket == self.p1): self.p1Ready = True - print("p1 ready") elif (socket == self.p2): self.p2Ready = True - print("p2 ready") else: return(0) if(self.p1Ready and self.p2Ready): - print("both ready, start game loop") - self.game_loop(); + print("both players are ready, starting game") + asyncio.create_task(self.gameLoop()) return(1) + + def leave(self, socket): + socket.game = None + if(socket == self.p1): + self.p1 = None + self.p1Ready =False + elif(socket == self.p2): + self.p2 = None + self.p2Ready =False + if(not self.started): + while(Game.waitingForPlayerLock): + time.sleep(0.05) + Game.waitingForPlayerLock = True + if(Game.waitingForPlayer == self): + Game.waitingForPlayer = None + if(self.p1 != None or self.p2 != None): + Game.waitingForPlayer = self + Game.waitingForPlayerLock = False + self.playerLeft = True - def game_loop(self): - print("AAAAAAAAAAAAAAAAAAA") + def sendPlayers(self, data): + data_raw = json.dumps({"type":"game","content":data}) + self.p1.sync_send(data_raw) + self.p2.sync_send(data_raw) + + async def gameLoop(self): + self.started = True + self.sendPlayers({"action":2}) + while(not self.end): + print("AAAAAAAAAAAAAAAAAAA") + await asyncio.sleep(1) + if(self.playerLeft): + self.end = True + print("A player left, stopping the game. (i know it's a very beautiful and complete game)") diff --git a/docker-compose/requirements/djangoserver/file/server/server/typeRequests/createAccount.py b/docker-compose/requirements/djangoserver/file/server/server/typeRequests/createAccount.py index d2ee396..8fa66fe 100644 --- a/docker-compose/requirements/djangoserver/file/server/server/typeRequests/createAccount.py +++ b/docker-compose/requirements/djangoserver/file/server/server/typeRequests/createAccount.py @@ -6,7 +6,7 @@ # By: edbernar +#+ +:+ +#+ # +# +#+#+#+#+#+ +#+ # +# Created: 2024/09/14 15:37:49 by tomoron #+# #+# # +# Updated: 2024/09/14 21:20:32 by tomoron ### ########.fr # +# # +# **************************************************************************** # + +async def leave(socket, content): + socket.game.leave(socket) diff --git a/docker-compose/requirements/djangoserver/file/server/server/typeRequests/gameActions/ready.py b/docker-compose/requirements/djangoserver/file/server/server/typeRequests/gameActions/ready.py index 9fa15d2..face1f1 100644 --- a/docker-compose/requirements/djangoserver/file/server/server/typeRequests/gameActions/ready.py +++ b/docker-compose/requirements/djangoserver/file/server/server/typeRequests/gameActions/ready.py @@ -6,13 +6,13 @@ # By: tomoron +#+ +:+ +#+ # # +#+#+#+#+#+ +#+ # # Created: 2024/09/13 23:41:12 by tomoron #+# #+# # -# Updated: 2024/09/14 00:21:43 by tomoron ### ########.fr # +# Updated: 2024/09/14 19:27:51 by tomoron ### ########.fr # # # # **************************************************************************** # -def ready(socket, content): +async def ready(socket, content): print("ready request") if(socket.game == None): socket.sendError("No game started", 9101) return; - socket.game.setReady(socket) + await socket.game.setReady(socket) diff --git a/docker-compose/requirements/djangoserver/file/server/server/typeRequests/gameActions/start.py b/docker-compose/requirements/djangoserver/file/server/server/typeRequests/gameActions/start.py index 3bed1c0..25dba61 100644 --- a/docker-compose/requirements/djangoserver/file/server/server/typeRequests/gameActions/start.py +++ b/docker-compose/requirements/djangoserver/file/server/server/typeRequests/gameActions/start.py @@ -6,11 +6,14 @@ # By: tomoron +#+ +:+ +#+ # # +#+#+#+#+#+ +#+ # # Created: 2024/09/11 17:07:08 by tomoron #+# #+# # -# Updated: 2024/09/13 23:46:25 by tomoron ### ########.fr # +# Updated: 2024/09/14 19:28:30 by tomoron ### ########.fr # # # # **************************************************************************** # from ...Game import Game -def start(socket, content): - game = Game(socket, content.get("with_bot", False)) +async def start(socket, content): + if(socket.game != None): + socket.sendError("Game already started", 9102) + Game(socket, content.get("with_bot", False)) + diff --git a/docker-compose/requirements/djangoserver/file/server/server/typeRequests/gameRequest.py b/docker-compose/requirements/djangoserver/file/server/server/typeRequests/gameRequest.py index 3e53e97..304b932 100644 --- a/docker-compose/requirements/djangoserver/file/server/server/typeRequests/gameRequest.py +++ b/docker-compose/requirements/djangoserver/file/server/server/typeRequests/gameRequest.py @@ -6,12 +6,13 @@ # By: tomoron +#+ +:+ +#+ # # +#+#+#+#+#+ +#+ # # Created: 2024/09/09 16:10:26 by tomoron #+# #+# # -# Updated: 2024/09/14 12:43:14 by tomoron ### ########.fr # +# Updated: 2024/09/14 19:22:52 by tomoron ### ########.fr # # # # **************************************************************************** # from .gameActions.start import start from .gameActions.ready import ready +from .gameActions.leave import leave # game request format : {"type":"game", "content":{"action": 1, ...}} @@ -19,24 +20,24 @@ from .gameActions.ready import ready # 0 : wait : tell the client to wait for an opponent # # 1 : opponent : tell the client the name of the opponent -# - id +# - id : 0 if it's a bot # - username # # 2 : go : the game started #client actions (actions sent by the client) : # 0 : start : starts a game -# - with_bot : true/false, is the second player a bot +# - with_bot : true/false (default : false), is the second player a bot # # 1 : ready : tell the server the game is ready to start # # 2 : leave : leave the game (or waiting screen) -action_list = [start, ready] -def gameRequest(socket, content): +action_list = [start, ready, leave] +async def gameRequest(socket, content): action = content["action"] if(action < 0 or action > len(action_list)): socket.sendError("Action out of range", 9100) return; - action_list[action](socket,content) + await action_list[action](socket,content) diff --git a/docker-compose/requirements/djangoserver/file/server/server/typeRequests/getAllListUser.py b/docker-compose/requirements/djangoserver/file/server/server/typeRequests/getAllListUser.py index f2bee42..994e5fe 100644 --- a/docker-compose/requirements/djangoserver/file/server/server/typeRequests/getAllListUser.py +++ b/docker-compose/requirements/djangoserver/file/server/server/typeRequests/getAllListUser.py @@ -6,7 +6,7 @@ # By: edbernar +#+ +:+ +#+ # # +#+#+#+#+#+ +#+ # # Created: 2024/08/03 15:10:23 by edbernar #+# #+# # -# Updated: 2024/08/28 18:22:43 by tomoron ### ########.fr # +# Updated: 2024/09/14 18:31:09 by tomoron ### ########.fr # # # # **************************************************************************** # @@ -14,11 +14,13 @@ import asyncio import json from django.db.models import Q from ..models import Message, User +from asgiref.sync import sync_to_async +@sync_to_async def getAllListUser(socket, content=None): - uid = socket.scope["session"].get("uid", 0) + uid = socket.id res = User.objects.filter(~Q(id=uid)) data = [] for x in res: data.append({"name":x.username, "pfp":x.pfp, "id":x.id}) - socket.send(text_data=json.dumps({"type": "all_list_user", "content": data})) + socket.sync_send(json.dumps({"type": "all_list_user", "content": data})) diff --git a/docker-compose/requirements/djangoserver/file/server/server/typeRequests/getPrivateListMessage.py b/docker-compose/requirements/djangoserver/file/server/server/typeRequests/getPrivateListMessage.py index bfec47b..e569d54 100644 --- a/docker-compose/requirements/djangoserver/file/server/server/typeRequests/getPrivateListMessage.py +++ b/docker-compose/requirements/djangoserver/file/server/server/typeRequests/getPrivateListMessage.py @@ -6,62 +6,29 @@ # By: edbernar +#+ +:+ +#+ # # +#+#+#+#+#+ +#+ # # Created: 2024/08/03 15:10:23 by edbernar #+# #+# # -# Updated: 2024/08/27 23:57:44 by tomoron ### ########.fr # +# Updated: 2024/09/14 18:31:45 by tomoron ### ########.fr # # # # **************************************************************************** # import asyncio import json from ..models import Message, User +from asgiref.sync import sync_to_async -#data = [ -# { -# "name": "Nessundorma", -# "status": "online", -# "pfp": "https://wallpapers-clan.com/wp-content/uploads/2023/05/cool-pfp-02.jpg", -# "id": 145564 -# }, -# { -# "name": "Succotash", -# "status": "offline", -# "pfp": "https://i.pinimg.com/200x/28/75/96/287596f98304bf1adc2c411619ae8fef.jpg", -# "id": 256981 -# }, -# { -# "name": "Astropower", -# "status": "online", -# "pfp": "https://ashisheditz.com/wp-content/uploads/2024/03/cool-anime-pfp-demon-slayer-HD.jpg", -# "id": 301547 -# }, -# { -# "name": "Assaultive", -# "status": "offline", -# "pfp": "https://i1.sndcdn.com/artworks-1Li0JIJrQGlojD3y-AEiNkw-t500x500.jpg", -# "id": 432448 -# }, -# { -# "name": "Redshock", -# "status": "offline", -# "pfp": "https://cdn.pfps.gg/pfps/7094-boy-pfp.png", -# "id": 543211 -# }, -# { -# "name": "Parley", -# "status": "offline", -# "pfp": "https://pbs.twimg.com/media/EscE6ckU0AA-Uhe.png", -# "id": 654123 -# } -#] - +@sync_to_async def getPrivateListUser(socket, content=None): # |TOM| Faire une requête à la base de données pour récupérer la liste des # utilisateurs qui doivent apparaitre dans la liste du chat privé # (ceux qui ont eu conversation avec l'utilisateur) # Si user existe pas, faire ça : socket.sendError("User not found", 9008) - id = socket.scope["session"].get("id", 0) + id = socket.id request = """ SELECT DISTINCT server_user.id AS id, username, pfp FROM server_user @@ -71,4 +34,4 @@ def getPrivateListUser(socket, content=None): for x in res: status = "online" if x.id in socket.onlinePlayers else "offline" data.append({"name":x.username, "status": status, "pfp":x.pfp, "id":x.id}) - socket.send(text_data=json.dumps({"type": "private_list_user", "content": data})) + socket.sync_send(json.dumps({"type": "private_list_user", "content": data})) diff --git a/docker-compose/requirements/djangoserver/file/server/server/typeRequests/login.py b/docker-compose/requirements/djangoserver/file/server/server/typeRequests/login.py index aacb0fe..7c471fb 100644 --- a/docker-compose/requirements/djangoserver/file/server/server/typeRequests/login.py +++ b/docker-compose/requirements/djangoserver/file/server/server/typeRequests/login.py @@ -6,10 +6,11 @@ # By: edbernar +#+ +:+ +#+ # # +#+#+#+#+#+ +#+ # # Created: 2024/09/09 14:31:30 by tomoron #+# #+# # -# Updated: 2024/09/14 00:30:59 by tomoron ### ########.fr # +# Updated: 2024/09/14 21:21:19 by tomoron ### ########.fr # # # # **************************************************************************** # -from channels.generic.websocket import WebsocketConsumer +from channels.generic.websocket import AsyncWebsocketConsumer +from asgiref.sync import sync_to_async +from typing import Union import json +import asyncio import django django.setup() @@ -31,15 +34,27 @@ functionRequest = [login, getPrivateListUser, getPrivateListMessage, from random import randint -class WebsocketHandler(WebsocketConsumer): +class WebsocketHandler(AsyncWebsocketConsumer): debugMode = True # format : {id : socket, ...} onlinePlayers = {} + @sync_to_async + def session_get(self, key, default=None): + return(self.scope["session"].get(key, default)) + + @sync_to_async + def session_set(self, key, value): + self.scope["session"][key] = value + + @sync_to_async + def session_save(self): + self.scope["session"].save() + def add_to_online(self, uid): if(not uid): - return + return(0) if(uid not in self.onlinePlayers): self.onlinePlayers[uid] = self return(1) @@ -47,56 +62,53 @@ class WebsocketHandler(WebsocketConsumer): return(0) def login(self, uid: int, username: str) -> int: - if(self.scope["session"].get("logged_in", False)): + if(self.session_get("logged_in", False)): return(0) if(not self.add_to_online(uid)): socket.sendError("Already logged in", 9012) return(0) - self.scope["session"]["logged_in"] = True - self.scope["session"]["id"] = uid - self.scope["session"]["username"] = username - self.scope["session"].save() + self.session_set("logged_in",True) + self.session_set("id",uid) + self.session_set("username",username) + self.session_save() self.logged_in = True self.id = uid self.username = username return(1) - def connect(self): + async def connect(self): self.logged_in = False self.game = None self.id = 0 self.username = None - self.accept() - if(self.scope["session"].get("logged_in", False)): - if(not self.add_to_online(self.scope["session"].get("id", 0))): - self.sendError("User already connected", 9013) - self.close() + await self.accept() + if(await self.session_get("logged_in", False)): + if(not self.add_to_online(await self.session_get("id", 0))): + jsonVar = {"type": "error", "content": "User already connected", "code": 9013} + await self.send(text_data=json.dumps(jsonVar)) + await self.close() return; - self.id = self.scope["session"].get("id",0) - self.username = self.scope["session"].get("username", None) - self.send(text_data=json.dumps({"type":"logged_in", "content":{ - "status":self.scope["session"].get("logged_in",False), - "username":self.scope["session"].get("username",None), - "id":self.scope["session"].get("id",0) + self.id = await self.session_get("id",0) + self.username = await self.session_get("username", None) + self.logged_in = True + await self.send(text_data=json.dumps({"type":"logged_in", "content":{ + "status":await self.session_get("logged_in",False), + "username":await self.session_get("username",None), + "id":await self.session_get("id",0) }})) - self.logged_in = self.scope["session"].get("logged_in", False) print("new client") - def disconnect(self, close_code): + async def disconnect(self, close_code): print("you can go, i am not mad, we never wanted you anyway") if(not self.logged_in): return ; - uid = self.scope["session"].get("id", 0) + uid = await self.session_get("id", 0) if(uid in self.onlinePlayers): del self.onlinePlayers[uid] + if(self.game !=None): + self.game.leave(self) - def receive(self, text_data): - print("someone is talking") - print("username is ", self.scope["session"].get("username")) - if(self.scope["session"].get("logged_in", False)): - print("user is logged in") - else: - print("user is not logged in") + async def receive(self, text_data): try: jsonRequest = json.loads(text_data) except json.JSONDecodeError: @@ -106,21 +118,37 @@ class WebsocketHandler(WebsocketConsumer): self.printDebug(jsonRequest, 0) if (jsonRequest["type"] in typeRequest): if (jsonRequest["type"] == "login" or jsonRequest["type"] == "create_account"): - functionRequest[typeRequest.index(jsonRequest["type"])](self, jsonRequest["content"]) + await functionRequest[typeRequest.index(jsonRequest["type"])](self, jsonRequest["content"]) else: - if (not self.scope["session"].get("logged_in", False)): + if (not await self.session_get("logged_in", False)): return; - functionRequest[typeRequest.index(jsonRequest["type"])](self, jsonRequest["content"]) + await functionRequest[typeRequest.index(jsonRequest["type"])](self, jsonRequest["content"]) else: self.sendError("Invalid type", 9004) except Exception as e: self.sendError("Invalid request", 9005, e) + + def sync_send(self, data: Union[dict,str]): + txt_data = None + if(type(data) is dict): + txt_data = json.dumps(data) + else: + txt_data = data + + try: + loop = asyncio.get_running_loop() + except RuntimeError: + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + loop.run_until_complete(self.send(text_data=txt_data)) + else: + loop.create_task(self.send(text_data=txt_data)) def sendError(self, message, code, error=None): try: jsonVar = {"type": "error", "content": message, "code": code} self.printDebug(jsonVar, 2, error) - self.send(text_data=json.dumps(jsonVar)) + self.sync_send(jsonVar) except Exception as e: if (self.debugMode): print("\033[0;31m|------ Error in sendError ------|\033[0;0m") diff --git a/listError.txt b/listError.txt index 6136515..b2a1383 100644 --- a/listError.txt +++ b/listError.txt @@ -31,3 +31,4 @@ - 9100 : Action out of range - 9101 : No game started +- 9102 : Game already started