Multithreaded game - updating, rendering and how to separate them

So, I’m working on a game engine, and I have made good progress. However, my engine is single-threaded, and the benefits of splitting the update and rendering into separate streams sound like a very good idea.

How should I do it? Single-threaded game engines (conceptually) are very easy to do, you have a loop in which you update → render → sleep → repeat. However, I cannot come up with a good way to unlock the update and rendering, especially if I change my update speeds (say, I go through the update cycle 25 times per second and have 60 frames per second for rendering) - what if I start the update halfway through the visualization cycle or vice versa?

+5
source share
7 answers

Put the update logic in some working class class Updater(implementation Runnable) and put the render in a separate working class. When you need to update data, let Updater queue this update, shared by both Updater and Producer. It would be most convenient to use a queue that already has built-in multi-threaded support, for example, a subclass BlockingQueue. For example code, see Javadoc for BlockingQueue.

Using a queue is natural if you need to display all changes, even outdated ones. If you want to make only the last change, use ConcurrentHashMapa queue instead.

, .

, - / . Executor Executors JDK, .

+6

, , , , :

0

1 Render 0

2 Render 1

3 Render 2

....

, , /

, ( , ),

+1

pojo , , fps, n, , singleton, , threadpool

0

, , . , , , . , , .

(, , , -).

0

, ( ).

  • ( )

  • ( .. 1 ... , , . , , iF )

  • Render thread ( , , , , )

, "" , . , , 1 , 2... , . , ... , , , , , - "".

public static volatile item . AtomicIntegerArray .

0

I would say add a field that indicates the stream needed to run and render, and the number of threads, if the stream number == a stream is required, then it is allowed to start and visualize and increase the required stream field until it reaches its maximum, then go back to 0. Alternatively, you can use one stream for a tick and another for rendering, it might be easier. Here is a sample code:

public Game() {
    this.tickThread=new Thread(this::tickLoop());
    this.renderThread=new Thread(this::renderLoop());
}
public void tickLoop() {
    //code for timing...
    while(running) {
        //more code for timing...
        tick();
    }
}
public void renderLoop() {
    //code for timing or syncing frames...
    while(running) {
        //more code for timing...
        render();
    }
}

Alternatively you can say:


| MyRunnable.java |

public interface MyRunnable
{
    public abstract void run(boolean toRender);
}

| MyThread.java |

public class MyThread extends Thread
{
    private boolean isRender;
    private MyRunnable runnable
    public MyThread(boolean isRender,MyRunnable runnable)
    {
        this.isRender=isRender;
        this.runnable=runnable;
    }
    public void run()
    {
        this.runnable.run(this.isRender);
    }
}

| Game.java |

public class Game extends /*JPanel/Canvas/JFrame/Some other component*/
{
    private MyThread tickThread;
    private MyThread renderThread;
    private boolean running;
    public Game()
    {
        super();
        tickThread=new MyThread(this::run);
        renderThread=new MyThread(this::run);
        //other constructor code
    }
    public void tick()
    {
        //tick code here
    }
    public void render()
    {
        //render code here
    }
    public void run(boolean isRender)
    {
        //timing variables
        while(running)
        {
            //timing code
            if(isRender)
            {
                this.render();
            }
            else
            {
                this.tick();
            }
        }
    }
}
0
source

All Articles