'How can I use pygame.Clock in a multiprocessing.Process call?
I'm an inexperienced python programmer, and for an interesting project I thought I'd try making a multiplayer LAN game (using pygame), with a server script and multiple client scripts connected through socket. I originally did this with threading, using two threads: one for the server, and one for the pygame section, since the program hangs due to socket.accept() if both are in one loop. However, this was incredibly laggy as soon as more than one client joined, so I decided to switch to using multiprocessing for performance. However, my pygame process crashes the script on startup due to TypeError: cannot pickle 'Clock' object exception. How can I fix this without changing the multiprocessing?
I've tried looking at the source code for pickle, multiprocessing, and pygame to try making my own class that handles the same functions as pygame.Clock, but I couldn't understand any of it, nor could I find anything online to help.
Relevant process definition:
class PygameProcess(mp.Process):
def __init__(self, groupQueue, dataArray, name=None):
super().__init__(name=name)
self.running = True
self.clock = pygame.time.Clock() # This is where the crash is
self.groupQueue = groupQueue # Queue for accepting new sprites
self.dataArray = dataArray # Shared memory array for sending the data to the clients' windows
self.spriteGroup = pygame.sprite.Group()
def run(self):
self.screen = pygame.display.set_mode((400,400))
pygame.display.set_caption("Multiplayer Test")
self.screen.fill((255,255,255))
pygame.display.flip()
while self.running:
while len(list(self.groupQueue.queue)) != 0:
sprite = self.groupQueue.get()
self.spriteGroup.add(sprite)
self.groupQueue.task_done()
self.clock.tick(60)
dataStr = ""
self.screen.fill((255,255,255))
for index, sprite in enumerate(self.spriteGroup):
sprite.update()
sprite.draw(self.screen)
dataStr += sprite.data
if index != len(self.spriteGroup)-1:
dataStr += ", "
# Commented out Thread code, so I don't lose it
#try:
# self.dataQueue.get_nowait()
#except queue.Empty:
# pass
#self.dataQueue.put(dataStr)
self.dataArray.value = tuple(dataStr)
pygame.display.update()
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.running = False
pygame.quit()
def stop(self):
self.running = False
Full code (apologies for lack of comments):
import pygame
import socket
import multiprocessing as mp
#import queue
import sys
# TODO: FIX "TypeError: can't pickle Clock objects"
'''CUSTOM PROCESSES'''
class ServerProcess(mp.Process):
def __init__(self, groupQueue, dataArray, name=None):
super().__init__(name=name)
self.server = "192.168.104.48" # Fred = "192.168.1.254" # Bert = "192.168.1.7" # School = "192.168.104.48"
self.port = 7777
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.connectedClients = []
self.clientProcesss = []
self.running = True
self.groupQueue = groupQueue
self.dataArray = dataArray
def run(self):
try:
self.socket.bind((self.server, self.port))
self.socket.settimeout(5)
except socket.error as e:
print(str(e))
else:
self.socket.listen(2)
print("Server started, awaiting connection")
while self.running:
try:
connection, address = self.socket.accept()
except socket.timeout:
pass
else:
if not self.running:
connection.close()
else:
print(f"Connected to {address}")
self.connectedClients.append(ConnectedClient(connection, address))
self.clientProcesses.append(ClientProcess(self.connectedClients[-1], self.group, self.data, name=f"Client{len(self.connectedClients)}"))
self.clientProcesses[-1].start()
for clientProcess in self.clientProcesses:
if not clientProcess.is_alive():
index = self.clientProcesss.index(clientProcess)
self.connectedClients.pop(index)
self.clientProcesses.pop(index)
finally:
print("Server shutting down...")
try:
self.stop()
except Exception:
pass
try:
self.socket.close()
except Exception:
pass
def stop(self):
for process in self.clientProcesses:
process.stop()
self.running = False
class ClientProcess(mp.Process):
def __init__(self, client, groupQueue, dataArray, name=None):
super().__init__(name=name)
self.client = client
self.running = True
self.group = groupQueue
self.dataArray = dataArray
def run(self):
self.client.connection.send(str.encode("Connected"))
request = ""
while self.running:
try:
data = self.client.connection.recv(2048)
request = data.decode("utf-8")
print(f"Received request of '{request}' from {self.client.address}")
if not data:
break
elif request == "Data":
#dataStr = self.dataQueue.get()
#try:
# self.dataQueue.put_nowait(dataStr)
#except queue.Full:
# pass
#self.client.connection.sendall(str.encode(dataStr))
#self.dataQueue.task_done()
self.client.connection.sendall(str.encode("".join(self.dataArray)))
elif request.split(", ")[0] == "Create Player":
try:
self.client.player = Player(tuple(int(x) for x in request.split(", ")[1][1:-1].split(",")), int(request.split(", ")[2]), int(request.split(", ")[3]), 10, 15)
self.groupQueue.put(self.client.player)
self.client.connection.sendall(str.encode("Creating your player"))
except ArrayError:
self.client.player = None
self.client.connection.sendall(str.encode("Player creation request denied, invalid parameters"))
print(f"{self.client.connection} attempted to make a player with invalid arguments")
elif request == "Left":
self.client.connection.sendall(str.encode("Turning Left"))
if len(list(self.client.player.turnRequests.queue)) < 2 and self.client.player.xVel == 0:
self.client.player.turnRequests.put("Left")
elif request == "Right":
self.client.connection.sendall(str.encode("Turning Right"))
if len(list(self.client.player.turnRequests.queue)) < 2 and self.client.player.xVel == 0:
self.client.player.turnRequests.put("Right")
elif request == "Up":
self.client.connection.sendall(str.encode("Turning Up"))
if len(list(self.client.player.turnRequests.queue)) < 2 and self.client.player.yVel == 0:
self.client.player.turnRequests.put("Up")
elif request == "Down":
self.client.connection.sendall(str.encode("Turning Down"))
if len(list(self.client.player.turnRequests.queue)) < 2 and self.client.player.yVel == 0:
self.client.player.turnRequests.put("Down")
elif request == "Disconnect":
self.client.connection.sendall(str.encode("Disconnecting..."))
print(f"Disconnecting {self.client.address}")
break
else:
print(f"Sending '{request}' to {self.client.address}")
self.client.connection.sendall(str.encode(request))
except Exception as e:
print("Error:", e)
break
print(f"Lost connection to {self.client.address}")
self.client.player.kill()
self.client.connection.close()
def stop(self):
self.running = False
class PygameProcess(mp.Process):
def __init__(self, groupQueue, dataArray, name=None):
super().__init__(name=name)
self.running = True
self.clock = pygame.time.Clock() # This is where the crash is
self.groupQueue = groupQueue # Queue for accepting new sprites
self.dataArray = dataArray # Shared memory array for sending the data to the clients' windows
self.spriteGroup = pygame.sprite.Group()
def run(self):
self.screen = pygame.display.set_mode((400,400))
pygame.display.set_caption("Multiplayer Test")
self.screen.fill((255,255,255))
pygame.display.flip()
while self.running:
while len(list(self.groupQueue.queue)) != 0:
sprite = self.groupQueue.get()
self.spriteGroup.add(sprite)
self.groupQueue.task_done()
self.clock.tick(60)
dataStr = ""
self.screen.fill((255,255,255))
for index, sprite in enumerate(self.spriteGroup):
sprite.update()
sprite.draw(self.screen)
dataStr += sprite.data
if index != len(self.spriteGroup)-1:
dataStr += ", "
# Commented out Thread code, so I don't lose it
#try:
# self.dataQueue.get_nowait()
#except queue.Empty:
# pass
#self.dataQueue.put(dataStr)
self.dataArray.value = tuple(dataStr)
pygame.display.update()
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.running = False
pygame.quit()
def stop(self):
self.running = False
'''CLASSES'''
class ConnectedClient():
def __init__(self, connection, address):
self.connection = connection
self.address = address
self.player = None
class Player(pygame.sprite.Sprite):
def __init__(self, colour, x, y, width, height):
super().__init__()
self.colour = colour
self.upFacingWidth = width
self.upFacingHeight = height
self.width = width
self.height = height
self.image = pygame.Surface([self.width,self.height])
self.image.fill(self.colour)
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
self.xVel = 0
self.yVel = 0
self.speed = 1
self.turnRequests = mp.Queue(2)
self.data = ""
def update(self):
self.rect.centerx += self.xVel
self.rect.centery += self.yVel
if self.rect.x % 5 == 0 and self.rect.y % 5 == 0:
invalidTurn = True
while invalidTurn and len(list(self.turnRequests.queue)) > 0:
currentTurn = self.turnRequests.get()
if currentTurn == "Left":
self.xVel = -1 * self.speed
self.yVel = 0
invalidTurn = False
self.height = self.upFacingWidth
self.width = self.upFacingHeight
self.image = pygame.Surface([self.width,self.height])
self.image.fill(self.colour)
if currentTurn == "Right":
self.xVel = self.speed
self.yVel = 0
invalidTurn = False
self.height = self.upFacingWidth
self.width = self.upFacingHeight
self.image = pygame.Surface([self.width,self.height])
self.image.fill(self.colour)
if currentTurn == "Up":
self.yVel = -1 * self.speed
self.xVel = 0
invalidTurn = False
self.height = self.upFacingHeight
self.width = self.upFacingWidth
self.image = pygame.Surface([self.width,self.height])
self.image.fill(self.colour)
if currentTurn == "Down":
self.yVel = self.speed
self.xVel = 0
invalidTurn = False
self.height = self.upFacingHeight
self.width = self.upFacingWidth
self.image = pygame.Surface([self.width,self.height])
self.image.fill(self.colour)
self.data = ""
dataTuple = (self.colour, self.rect.x, self.rect.y, self.width, self.height)
for index, item in enumerate(dataTuple):
if isinstance(item, tuple):
for secondaryIndex, secondaryItem in enumerate(item):
self.data += str(secondaryItem)
if secondaryIndex != len(item)-1:
self.data += ","
else:
self.data += str(item)
if index != len(dataTuple)-1:
self.data += ":"
print(self.data)
def draw(self, surface):
surface.blit(self.image, (self.rect.x, self.rect.y))
'''SUBROUTINES'''
'''MAIN'''
if __name__ == "__main__":
spriteGroupQueue = mp.Queue()
currentDataArray = mp.Array("d", tuple(""))
serverProcess = ServerProcess(spriteGroupQueue, currentDataArray, name = "Server")
serverProcess.start()
pygameProcess = PygameProcess(spriteGroupQueue, currentDataArray, name = "Pygame")
pygameProcess.start()
threadsAlive = True
while threadsAlive:
if not serverProcess.is_alive() and pygameProcess.is_alive():
pygameProcess.stop()
threadsAlive = False
if not pygameProcess.is_alive() and serverProcess.is_alive():
serverProcess.stop()
threadsAlive = False
sys.exit(0)
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|
