There are multiple ways to go about this.
Usually, people would use worker threads, and have their server run there, and maybe even dispatch request handling in yet another threaded worker pool. It works but it is meh at best, with all the issues from thread parallelism (lock and joy).
Another way would be to handle sockets using an I/O selector, which is a system object that can tell you if a resource can be used (readable/writable state). So you would register your sockets to this central selector, ask it the state of the file descriptors, and perform your operation on sockets that have data, so the call to recv(buffer)
won’t block, because you know there is data buffered, ready to be read.
The selectors
module from Python standard library does that, and it is what I was using before.
Now comes asyncio
, the idea is that you will have an “event loop” provided by the framework, and this loop will be your main application main loop, and will block everything until the thing stops somehow. In my case I run the loop in bursts, so that it only blocks for half the frame time, and then lets Blender/Python continue, then back to blocking on the following frame, etc…
The event loop job in this case is to monitor I/O operations (mostly network I/O), but also bundles a coroutine/task scheduler: You can basically write asynchronous code that will pause itself while waiting for something else, example:
@asynchronous
async def mainloop(self):
ws = self.websocket
while not ws.closed:
try:
message = await ws.recv()
except exceptions.ConnectionClosed:
print('connection closed.')
bge.logic.endGame()
break
type, raw = parse_message(message)
handler = self.HANDLERS.get(type, None)
if handler is None:
print(f'Unknown message type: {type}')
continue
handler(self, raw)
# [...]
From: https://gitlab.com/marechal-p/bgmc29-retro/blob/master/retro/network/message_handler.py#L46
In the above function (which should be registered in the asyncio event loop to run), once we will execute the await ws.recv()
line, if data is not yet ready, the event-loop will pause the execution of the function, and look for someone else to be executed, like some other coroutine that would be ready to run (data arrived, some future resolved, etc…).
Event loops are a wonderful way of writing asynchronous applications in my opinion, so having asyncio
run in the BGE is really a big step forward to me.
Only issue is that asyncio
makes the developer having to know a lot of things, while if you look at how NodeJS works: most people don’t even know there is an event loop running, yet everyone is able to use it.
TL;DR: I just run my network operations inside asyncio
's event loop, which I run in bursts for half a frame.