I have been thinking for some time about how to optimise Pygame Zero games
on Raspberry Pi. Most Pi models have multiple cores and an obvious
improvement is to parallelise. Logic has to be synchronous but I would like
to offload painting the screen to a separate thread.

The problem I would have is in passing references to mutable objects
between threads. Primarily this applies to Surface objects, which are
mutable and expensive to copy. If I have a draw thread that maintains a
queue of screen blit operations, I want the queue to hold references to
surface data that won't change even if I later mutate the surfaces in the
logic thread.

If I was writing a game myself, I could work around this easily by taking
copies of surfaces that I want to mutate again in a different thread. But
as a game framework I don't know how users will use the surfaces in their
code. I would have to copy everything in case the logic thread updates it -
and that is unacceptable overhead given that I expect mutation to be very
rare.

A solution could be copy-on-write clones for mutable objects. For example,
Surface.__copy__() would return a new PyObject referring to the same SDL
surface, incrementing a refcount for that surface data. All mutation
methods would create a single-user copy of the surface if the refcount was
higher than 1. Code that doesn't use clones would pay a small overhead in
performing refcount checks. Refcount checks could be guarded by the GIL.

This solves my problem, as I can then clone every surface when passing it
to a draw thread, and this becomes a very cheap operation. If the logic
thread does mutate a surface it creates a copy in that thread.

How feasible is this? Are there other applications that could benefit from
it? I could do it in Pygame Zero by wrapping all Surface objects in a
Python class but this makes Pygame Zero a thicker and slower layer around
Pygame (the goal is to be "training wheels" for Pygame) and creates more
pitfalls when users dig into Pygame proper.

Reply via email to