Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: How to display window in a separate thread/process?  (Read 6618 times)

0 Members and 1 Guest are viewing this topic.

kolofsson

  • Full Member
  • ***
  • Posts: 100
    • View Profile
How to display window in a separate thread/process?
« on: November 17, 2011, 08:30:02 pm »
Hi. I'm practicing with PySFML. I made a simple app that measures user response with a timer. To avoid mad refresh rate I enabled vertical sync. This is where the problems start. Without Vsync the results were accurate, but with Vsync on they are rounded according to the refresh rate of the screen. For example on my PC i keep getting results like 0,2333 second (14 / 60). This is because the window.display() function waits for the vertical sync and pauses the whole app. For me, this is unacceptable.

So there goes my question: How to move the display code to a separate thread or process? I read some tutorials on thread, threading and multiprocessing libraries, but they all seem to have a problem in accessing shared objects. I want to handle input and drawing in separate threads, and both reside in the same sf.window object. Is there a way to solve this?

Big thanks up front for help!

bastien

  • Full Member
  • ***
  • Posts: 231
    • View Profile
    • http://bastien-leonard.alwaysdata.net
How to display window in a separate thread/process?
« Reply #1 on: November 18, 2011, 05:15:15 pm »
Usually, this happens because you have some state growing with every frame instead of, say, every second. You want to avoid making your code dependent on frame rate, otherwise the game will progress differently based on the machine's performance and the game configuration (if the user can tweak frame rate or vertical synchronization). If you look at the pong example, the balls move in proportion with window.frame_time rather than in with every frame.
I don't know what you're trying to do exactly, so I can't really help you.
I don't think multithreading is the right way to solve this.
Check out pysfml-cython, an up to date Python 2/3 binding for SFML 2: https://github.com/bastienleonard/pysfml-cython

kolofsson

  • Full Member
  • ***
  • Posts: 100
    • View Profile
How to display window in a separate thread/process?
« Reply #2 on: November 18, 2011, 07:36:03 pm »
Threading IS the right way to do it, as Laurent told me himself. I want to depend my calculations on time as well, I just do not want to be limited to 60 frames per second. I would prefer 600 frames, or as much frames as the machine can handle. Differences between machines won't occur because all movement will be based on frame time.

So I'm asking if you know how to do implement it in python?

The design of my app:

thread 1: (done e.g. 600 times per second)
- get window events (keyboard input)
- send/receive network packets
- do physics calculations
- move sprites

thread 2: (done e.g. 60 times per second)
- a loop that draws sprites and displays them on screen

I just don't know how to share the window object and the sprites between two threads. Man, I don't even know how to share an object between two functions. Should I use global / static or keep passing the object by reference somehow?

bastien

  • Full Member
  • ***
  • Posts: 231
    • View Profile
    • http://bastien-leonard.alwaysdata.net
How to display window in a separate thread/process?
« Reply #3 on: November 19, 2011, 12:23:29 am »
Quote from: "kolofsson"
I just don't know how to share the window object and the sprites between two threads. Man, I don't even know how to share an object between two functions. Should I use global / static or keep passing the object by reference somehow?


Global variables need to be used sparingly, and I would recommend to avoid them because they can be misused easily in Python:

Code: [Select]
global_var = 0

class Thread(threading.Thread):
    def run(self):
        global_var = 1


This code doesn't modify the global object, it creates local object named global_var. You need to write “global global_var” to let Python know you want to use a global variable.

If you pass a reference to the thread, you need to be careful too, because some basic types are immutable. If you pass a bool to a function, the function can't modify the bool object itself (the one referenced from the caller), so all it can do is create a new bool object and store it in a local reference.

If you just create a class containing the thread data, you won't have these problems:

Code: [Select]
import threading

class ThreadData(object):
    def __init__(self, window):
        self.window = window


class Thread(threading.Thread):
    def __init__(self, data):
        threading.Thread.__init__(self)
        self.data = data

    def run(self):
        for event in self.window.iter_events():
            pass


In your thread's loop, you can use a clock to retrieve the elapsed time since the last loop iteration, decide if you need to go idle, and if so call time.sleep().
I'm not familiar with multithreading with SFML, but I don't remember reading that you need to be especially careful about the window object when handling its events in one thread and displaying stuff on it in another thread. But maybe I've just missed/forgotten some information.

Now, the thing with Python is, only one thread will run at once. What I can do in the binding is allow another thread to run while waiting for a function to finish, like window.display(). But I haven't done this kind of optimization yet because I'd like to have some real world example of the performance benefit it gives.
An easy to have true parallelism is to use processes instead of threads, but processes don't share memory by default, so if you want to share e.g. sprites it's easier with threads.
If you have some performance heavy code, you can also write it in C or in Cython and then nothing the threads from running at the same time (as long as you release the global interpreter lock).
Check out pysfml-cython, an up to date Python 2/3 binding for SFML 2: https://github.com/bastienleonard/pysfml-cython

kolofsson

  • Full Member
  • ***
  • Posts: 100
    • View Profile
How to display window in a separate thread/process?
« Reply #4 on: November 20, 2011, 02:13:39 pm »
You see, the problem is that I would not like to tell the app to sleep, I would not like to waste the computational power, I would prefer to handle window events in the meantime. The window.display with vsync enabled goes to sleep and waits until it is safe to write the buffer.

I actually figured out a workaround that looks like this:

Code: [Select]
import time
import sf

def main():
window = sf.RenderWindow(sf.VideoMode(800, 600), 'Window', sf.Style.DEFAULT)
window.vertical_sync_enabled = True

last_display = time.clock()
while window.opened:
for event in window.iter_events():
#handle events

if time.clock() - last_display > 0.016:
window.clear(sf.Color(200,   0,   0))
window.display()
last_display = time.clock()

if __name__ == '__main__':
main()


The crucial part is if time.clock() - last_display > 0.016:. Without it I get 60 FPS. With it I get 6000 FPS! That's because This little if triggers the display just before the Vsync window get opened, which makes the display wait time minimal.

However, the 0.016 value will only work for 60 Hz. For different refresh rates bad things will happen. Btw, is there a way to retrieve screen refresh rate in SFML?

bastien

  • Full Member
  • ***
  • Posts: 231
    • View Profile
    • http://bastien-leonard.alwaysdata.net
How to display window in a separate thread/process?
« Reply #5 on: November 20, 2011, 11:42:38 pm »
Quote from: "kolofsson"
You see, the problem is that I would not like to tell the app to sleep, I would not like to waste the computational power, I would prefer to handle window events in the meantime. The window.display with vsync enabled goes to sleep and waits until it is safe to write the buffer.


Unless you have very specific needs, there's no reason why you would want more than, say, 60 FPS. Your application can run for an extended period of time, and it will waste a lot of resource if you try to avoid sleeping after each iteration of the main loop.

Quote
I actually figured out a workaround that looks like this:

Code: [Select]
import time
import sf

def main():
window = sf.RenderWindow(sf.VideoMode(800, 600), 'Window', sf.Style.DEFAULT)
window.vertical_sync_enabled = True

last_display = time.clock()
while window.opened:
for event in window.iter_events():
#handle events

if time.clock() - last_display > 0.016:
window.clear(sf.Color(200,   0,   0))
window.display()
last_display = time.clock()

if __name__ == '__main__':
main()


That's kind of scary. :)

Quote from: "kolofsson"
The crucial part is if time.clock() - last_display > 0.016:. Without it I get 60 FPS. With it I get 6000 FPS! That's because This little if triggers the display just before the Vsync window get opened, which makes the display wait time minimal.


To be clear, the window only gets opened at the start. window.opened simply returns True if the window is currently opened, until you call window.close().

Quote from: "kolofsson"
However, the 0.016 value will only work for 60 Hz. For different refresh rates bad things will happen. Btw, is there a way to retrieve screen refresh rate in SFML?


Not that I know of. You can probably get it with OpenGL, but you can't do OpenGL calls with this binding currently.
Check out pysfml-cython, an up to date Python 2/3 binding for SFML 2: https://github.com/bastienleonard/pysfml-cython