如何在全新的线程上启动某个类的每个实例?

时间:2022-11-25 08:19:15

I'm writing a game for fun where players have sprites that can jump around and shoot lasers. It can have up to three players. My class Sprite is the same for all three players, just has a different control layout for each depending on the player # it is given in its construction. Sprite uses a KeyListener to function.

我正在写一个有趣的游戏,玩家可以跳跃并射击激光的精灵。它最多可以有三名玩家。我的班级Sprite对于所有三个玩家来说都是相同的,只是根据玩家#的结构给出了不同的控制布局。 Sprite使用KeyListener来运行。

In order for my to have multiple players doing things simultaneously (like shooting lasers or jumping) I need to have each Sprite object that is created be on a separate thread. I know I can use implements Runnable on the Sprite class, however this only runs the code in the run() method on the new thread. This doesn't work because Sprite has keyPressed() and other such things in it that won't be on the new thread.

为了让我有多个玩家同时做事(比如拍摄激光或跳跃),我需要让每个创建的Sprite对象都在一个单独的线程上。我知道我可以在Sprite类上使用implements Runnable,但是这只会在新线程的run()方法中运行代码。这不起作用,因为Sprite有keyPressed()和其他不会在新线程上的东西。

The one thought I had was to use a "helper" class and have that implements Runnable then in its run() method create the new Sprite object. This seems like a kind of messy approach however. Is there any way for my to create all new Sprite objects on a brand new thread (KeyListeners and all included on this thread)?

我认为我曾经想过使用一个“帮助器”类并实现Runnable然后在其run()方法中创建新的Sprite对象。然而,这似乎是一种混乱的方法。有没有办法让我在一个全新的线程上创建所有新的Sprite对象(KeyListeners和所有包含在这个线程上)?

Code:

public class Sprite() implements KeyListener { //I want this class on a brand new thread
    int x;
    int y;
    int width;
    int height;
    Image spriteImage; 

    //code/methods for stuff

    //key listeners:

    @Override
    public void keyPressed(KeyEvent arg0) {
        // TODO Auto-generated method stub

    }

    @Override
    public void keyReleased(KeyEvent arg0) {
        // TODO Auto-generated method stub

    }

    @Override
    public void keyTyped(KeyEvent arg0) {
        // TODO Auto-generated method stub

    }

}

Current Solution:

public class SpriteStarter(/* Sprite class parameters go here */) implements Runnable{

    void run() {
        Sprite s = new Sprite(/*params*/);
    }

}

...

class Linker() {
    public static void main(String args[]) {
        SpriteStarter s1 = new SpriteStarter();
        SpriteStarter s2 = new SpriteStarter();
        Thread t1 = new Thread(s1);
        Thread t2 = new THread(s2);
        t1.start();
        t2.start();
    }
}

Edit:

Okay, after A LOT of great feedback, its become obvious to me that my game should be a single-threaded thing. I apologize for not realizing that, I haven't done much game programming so this was a new thing for me. My new thought is too have an ArrayList that on keyPressed() fires adds the pressed keys to the list. Than in the Sprite classes I'll have an update() method which looks at the pressed keys and updates coordinates accordingly. Update() will then be called at a fixed interval through a java.awt.Timer. This seems like it may work to me but I'm not sure so let me know! Thanks again to everyone. Also, I'd still apreciate an answer to the original question (being: how to start every instance of a class on a new thread) since it could be helpful for future programs.

好的,经过很多很好的反馈后,对我来说很明显我的游戏应该是一个单线程的东西。我为没有意识到这一点道歉,我没有做太多的游戏编程,所以这对我来说是个新事物。我的新想法也有一个ArrayList,在keyPressed()上触发将按下的键添加到列表中。在Sprite类中,我将有一个update()方法,它查看按下的键并相应地更新坐标。然后将通过java.awt.Timer以固定间隔调用Update()。这似乎对我有用,但我不确定所以让我知道!再次感谢大家。此外,我仍然会回答原始问题的答案(如何在新线程上启动类的每个实例),因为它可能对将来的程序有所帮​​助。

1 个解决方案

#1


7  

First off let's get something straight: objects don't run on threads. They don't run on anything, really. They sit in memory and wait for some thread to execute their methods. This is why you can have race conditions. Two threads may attempt to access the same memory (perhaps on the same object) at once. Onto your question.

首先让我们直截了当:对象不会在线程上运行。真的,他们不会做任何事情。他们坐在内存中等待一些线程来执行他们的方法。这就是为什么你可以有竞争条件。两个线程可能会一次尝试访问同一个内存(可能在同一个对象上)。在你的问题上。


Take a few breaths and think about the design. Your input is not multi-threaded (at least I'm guessing so). Events come in one by one to your application from some device on the operating system (or, based on your comment, from a framework abstraction such as a window panel). Usually, updating a sprite only involves trivial math. This can be done in-line on the thread feeding you events.

几声呼吸,想想设计。你的输入不是多线程的(至少我猜是这样)。事件从操作系统上的某个设备逐个进入您的应用程序(或者,基于您的注释,来自框架抽象,例如窗口面板)。通常,更新精灵只涉及琐碎的数学。这可以在为您提供事件的线程上在线完成。

Further, you'll probably incur more overhead starting a thread for every new event (if you want to do what you're describing) than if you simply perform the calculation in-line. On top of that, what happens if you're in the middle of processing one event and a new one comes in? You'd need to block submission of the events to each thread (making the threads useless) or queue up events in thread-local queues.

此外,对于每个新事件(如果要执行您所描述的操作),您可能会产生更多的开销,而不是简单地执行内联计算。最重要的是,如果您正在处理一个事件并且有一个新事件进入,那么会发生什么?您需要阻止向每个线程提交事件(使线程无效)或在线程本地队列中排队事件。

But.. for entertainment.. let's say updating each sprite has the potential to take a very long time (this is silly, your game would be unplayable)...

但是..对于娱乐......让我们说更新每个精灵有可能需要很长时间(这很愚蠢,你的游戏将无法播放)......

You want one thread for each sprite. On each thread you need a message queue. When you start each thread you block on the message queue until a message arrives. When a message arrives the thread pops it off the queue and processes it. You need to encode the event in the message. The message needs to be passed by value into the queue. For simplicity, the message and the event can be the same class.

你想为每个精灵一个线程。在每个线程上,您需要一个消息队列。当您启动每个线程时,您将阻止消息队列,直到消息到达。当消息到达时,线程将其从队列中弹出并处理它。您需要在消息中对事件进行编码。消息需要通过值传递到队列中。为简单起见,消息和事件可以是同一个类。

It would be easiest to just have one listener for events and have that listener dispatch the appropriate events to concerned sprites. But if you want each sprite to listen for its own events, you simply add them to the queue for the thread processing the sprite's events from within the sprite itself.

只有一个事件监听器并让侦听器将相应的事件分派给相关的精灵是最容易的。但是如果你想让每个sprite都监听它自己的事件,你只需将它们添加到队列中,以便线程处理sprite本身内的sprite事件。

package sexy.multithreaded.sprites;

public class GameDriver implements EventListener {
    final EventDispatcher dispatcher;
    final Framework framework;
    final List<Sprite> sprites;

    GameDriver(Framework framework) {
        framework.addEventListener(self);
        self.framework = framework;
        sprites = new ArrayList<>();
        dispatcher = new EventDispatcher(sprites);
    }

    public static void main(String[] args) {
        // register for events form your framework
        Framework f = new Framework(); // or window or whatever
        new GameDriver(f).startGame(Integer.parseInt(args[0]));
    }

    void startGame(int players) {
        // initialize game state
        for (int player = 0; player <= players; player++) {
            Sprite s = new Sprite(player);
            sprites.add(s);
            s.start();
        }
        // and your event processing thread
        dispatcher.start();

        // loop forever
        framework.processEvents();
    }

    @Override
    void onEvent(Event e) {
        if (e == Events.Quit) {
            dispatcher.interrupt();
        } eles {
            dispatcher.q.put(e);
        }
    }
}

class EventDispatcher extends Thread implements Runnable {
    // setup a queue for events
    final Queue<Event> q;
    final List<Sprite> sprites;

    EventDispatcher(List<Sprite> sprites) {
        super(this, "Event Dispatcher");
        this.sprites = sprites;
        q = new BlockingQueue<>();
    }

    @Override
    void run() {
        while (!interrupted()) {
            Event e = q.take();
            getSpriteForEvent(e).q.put(e);
        }
        for (Sprite s : sprites) {
            s.interrupt();
        }
    }
}

class Sprite extends Thread implements Runnable {
    final int num;
    final Queue<Event> q;

    Sprite(int num) {
        super(this, "Sprite " + num);
        self.num = num;
        q = new BlockingQueue<>();
    }

    @Override
    void run() {
        while (!interrupted()) {
            Event e = q.take();
            handle(e);
        }
    }

    void handle(Event e) {
        // remember we assumed this takes a really long time..
        // but how do I know how to calculate anything?
        switch (e) {
            case Events.UP:
                   // try to do something really long...
                   waitForUpvoteOn("a/35911559/1254812");
               break; // (;
            ...
        }
    }
}

Now you have new problems to solve. Your game needs a clock. Events need to be batched into time windows which may or may not correlate directly to frames. What happens when one event comes in and a sprite is still processing an old event? Will you cancel processing of the old event or will you drop a frame? You also have to manage the size of your queues--can't produce more events than you can consume.

现在你有新问题需要解决。你的游戏需要一个时钟。需要将事件分组到时间窗口中,这些窗口可能会或可能不会直接与帧相关联。当一个事件进来并且精灵仍在处理旧事件时会发生什么?你会取消旧活动的处理还是会丢帧?您还必须管理队列的大小 - 不能产生比您可以消耗的更多事件。

The point is there has to be a source of truth for determining game state. A referee.. if you will. Turns out it's usually easiest to process all your events on a single thread. Think about it, if each sprite/thread has a ref then they still have to synchronize their individual world views. This effectively serializes the processing of game logic.

关键是必须有确定游戏状态的真相来源。裁判..如果你愿意的话。事实证明,在单个线程上处理所有事件通常最简单。想一想,如果每个sprite / thread都有一个ref,那么他们仍然需要同步他们各自的世界观。这有效地序列化了游戏逻辑的处理。

Let's add the timer and drawing:

让我们添加计时器和绘图:

class GameDriver ... {
    static final DELTA = 10; // ms
    final Timer timer;
    ...
    GameDriver(...) {
        ...
        timer = new Timer(dispatcher, DELTA);
        dispatcher = new EventDispatcher(sprites, f.canvas(), map);
    }

    void startGame(...) {
        ...
        // and your event processing thread and timer
        dispatcher.start();
        timer.start();   
        ...
    }

    @Override
    void onEvent(Event e) {
        if (e == Events.Quit) {
            timer.stop();
            dispatcher.interrupt();
        } else {
            if (!dispatcher.q.offer(e)) {
                // Oh no! We're getting more events than we can handle.
                // To avoid getting into this situation you can try to:
                //   1. de-dupe/coalesce/buffer events
                //   2. increase your tick interval (decrease frame rate)
                //   3. drop events (I shot you I swear!)
            }
        }
    }
}

class EventDispatcher ... {
    // setup a queue for events
    final Queue<Event> q;
    final Canvas canvas;

    EventDispatcher(List<Sprite> sprites, Canvas c) {
        super(this, "Event Dispatcher");
        q = new BlockingQueue<>();
        canvas = c;
    }

    @Override
    void tick() {
        for (Sprite s : sprites) {
            canvas.push();
            s.draw(canvas);
            canvas.pop();
        }
    }
}

class Sprite ... implements Drawable ... {
    final Bitmap bitmap;
    final Matrix matrix;
    ...
    Sprite(int num) {
        ...
        URL url = Sprite.class.getResource("sprites/player-"+num+".bmp");
        bitmap = new Bitmap(url);
        matrix = new Matrix();
    }

    @Override
    void draw(Canvas c) {
        c.apply(matrix);
        c.paint(bitmap);
    }

    void handle(Event e) {
        switch (e) {
            case Events.Left:
               matrix.translate(GameDriver.DELTA, 0);
               break;
            case Events.Down:
               matrix.translate(0, GameDriver.DELTA);
            ...
        }
    }
}

And after you cut out the useless threads for each sprite:

在你为每个精灵切掉无用的线程之后:

class GameDriver ... {
    void startGame(...) {
        // don't need to start() the sprites anymore..
        ...
            // give sprites a starting position
            sprite.matrix.translate(0, player);
    }
}

class EventDispatcher extends Thread implements Runnable {

    final Map<Matrix, Sprite> map;
    ...
    EventDispatcher(...) {
        ...
        map = new HashMap<>();
    }

    ...
    @Override
    void tick() {
        for (Sprite s : sprites) {
            // assuming we gave matrix a map-unique hash function
            checkBounds(s);
            map.put(s.matrix, s);
        }
        // process collisions or otherwise apply game logic
        applyLogic(map);
        map.clear();

        // draw the sprites (or use yet another thread)
        for (Sprite s : sprites) {
            canvas.push();
            s.draw(canvas);
            canvas.pop();
        }
    }

    @Override
    void run() {
        try {
            while (!interrupted()) {
                Event e = q.take();
                getSpriteForEvent(e).handle(e)
            }
        } catch (InterruptedException e) {
        } finally {
            for (Sprite s : sprites) {
                s.interrupt();
            }
        }
    }
    ...
}

class Sprite implements Drawable {
    ...
    // scratch the run method and thread constructor
    ...
}

I don't write games so I probably got something wrong..

我不写游戏,所以我可能出了点问题。


Anyway, there are a few takeaways. Recall that your computer has a fixed number of cores. Any number of threads greater than the number of cores will mean you have to switch context between threads. One thread is paused, its registers and stack saved, and a new thread is loaded. This is what your operating system's scheduler does (if it supports threads, as most do).

无论如何,有一些外卖。回想一下,您的计算机具有固定数量的核心。任何数量的线程大于核心数将意味着您必须在线程之间切换上下文。暂停一个线程,保存其寄存器和堆栈,并加载新线程。这是您的操作系统的调度程序所做的事情(如果它支持线程,就像大多数一样)。

So an unbounded number of sprites and/or other game objects each backed by it's own thread is just about the worst design you can imagine. It can really drop your tick rate.

所以一个无限数量的精灵和/或其他游戏对象,每个都由它自己的线程支持,这是你能想象到的最糟糕的设计。它可以真正降低你的滴答率。

Second, as I've already alluded to, in order to avoid race conditions (game checks position of sprite whilst sprite's thread is in the middle of updating it) you have to synchronize access to sprites data, anyway. If you can't compute your logic within one tick on one thread, perhaps you can explore having a work queue to perform sprite updates. But not one thread per sprite.

其次,正如我已经提到的那样,为了避免竞争条件(游戏检查sprite的位置,而sprite的线程正在更新它的中间),你必须同步访问sprite数据。如果您无法在一个线程上的一个刻度内计算逻辑,那么您可以探索具有工作队列来执行精灵更新。但每个精灵不是一个线程。

This is why, as suggested in a comment, 3 threads is a good ballpark. One to interface with the OS. One to process game logic. One to render graphics. (Leaving room for your GC thread if you're using Java.)

这就是为什么,如评论中所建议的,3个线程是一个很好的球场。一个与操作系统接口。一个来处理游戏逻辑。一个渲染图形。 (如果您使用的是Java,则为GC线程留出空间。)

Another way to think of it is that your job is to find the smallest window in which you can process input, resolve game state, and emit rendering events for the entire game. Then, just repeat this over and over again. The smaller your window the smoother your game and the higher your frame rate.

另一种思考方式是你的工作是找到最小的窗口,你可以在其中处理输入,解决游戏状态,并为整个游戏发出渲染事件。然后,一遍又一遍地重复这个。窗口越小,游戏越平滑,帧速率越高。


Finally, I should mention that the model you seek does actually arise in real world game design but for other reasons. Imagine a multiplayer game with many clients and a server (each effectively representing a new thread of execution). Each client will process its own input events, batch them, translate them to game events, and feed the game events into a server. The network becomes your serialization layer. The server will slurp up events, resolve game state, and send a reply back to the clients. The clients will accept the reply, update their local state, and render it. But the server sure as hell isn't going to wait more than a few frames for slow clients.

最后,我应该提一下,你所寻求的模型确实出现在现实世界的游戏设计中,但出于其他原因。想象一下拥有许多客户端和服务器的多人游戏(每个都有效地代表一个新的执行线程)。每个客户端将处理自己的输入事件,批量处理,将其转换为游戏事件,并将游戏事件提供给服务器。网络成为您的序列化层。服务器将淹没事件,解决游戏状态,并将回复发送回客户端。客户端将接受回复,更新其本地状态并进行呈现。但是服务器肯定不会为慢速客户端等待几帧。

Lag sux, my friend.

Lag sux,我的朋友。

#1


7  

First off let's get something straight: objects don't run on threads. They don't run on anything, really. They sit in memory and wait for some thread to execute their methods. This is why you can have race conditions. Two threads may attempt to access the same memory (perhaps on the same object) at once. Onto your question.

首先让我们直截了当:对象不会在线程上运行。真的,他们不会做任何事情。他们坐在内存中等待一些线程来执行他们的方法。这就是为什么你可以有竞争条件。两个线程可能会一次尝试访问同一个内存(可能在同一个对象上)。在你的问题上。


Take a few breaths and think about the design. Your input is not multi-threaded (at least I'm guessing so). Events come in one by one to your application from some device on the operating system (or, based on your comment, from a framework abstraction such as a window panel). Usually, updating a sprite only involves trivial math. This can be done in-line on the thread feeding you events.

几声呼吸,想想设计。你的输入不是多线程的(至少我猜是这样)。事件从操作系统上的某个设备逐个进入您的应用程序(或者,基于您的注释,来自框架抽象,例如窗口面板)。通常,更新精灵只涉及琐碎的数学。这可以在为您提供事件的线程上在线完成。

Further, you'll probably incur more overhead starting a thread for every new event (if you want to do what you're describing) than if you simply perform the calculation in-line. On top of that, what happens if you're in the middle of processing one event and a new one comes in? You'd need to block submission of the events to each thread (making the threads useless) or queue up events in thread-local queues.

此外,对于每个新事件(如果要执行您所描述的操作),您可能会产生更多的开销,而不是简单地执行内联计算。最重要的是,如果您正在处理一个事件并且有一个新事件进入,那么会发生什么?您需要阻止向每个线程提交事件(使线程无效)或在线程本地队列中排队事件。

But.. for entertainment.. let's say updating each sprite has the potential to take a very long time (this is silly, your game would be unplayable)...

但是..对于娱乐......让我们说更新每个精灵有可能需要很长时间(这很愚蠢,你的游戏将无法播放)......

You want one thread for each sprite. On each thread you need a message queue. When you start each thread you block on the message queue until a message arrives. When a message arrives the thread pops it off the queue and processes it. You need to encode the event in the message. The message needs to be passed by value into the queue. For simplicity, the message and the event can be the same class.

你想为每个精灵一个线程。在每个线程上,您需要一个消息队列。当您启动每个线程时,您将阻止消息队列,直到消息到达。当消息到达时,线程将其从队列中弹出并处理它。您需要在消息中对事件进行编码。消息需要通过值传递到队列中。为简单起见,消息和事件可以是同一个类。

It would be easiest to just have one listener for events and have that listener dispatch the appropriate events to concerned sprites. But if you want each sprite to listen for its own events, you simply add them to the queue for the thread processing the sprite's events from within the sprite itself.

只有一个事件监听器并让侦听器将相应的事件分派给相关的精灵是最容易的。但是如果你想让每个sprite都监听它自己的事件,你只需将它们添加到队列中,以便线程处理sprite本身内的sprite事件。

package sexy.multithreaded.sprites;

public class GameDriver implements EventListener {
    final EventDispatcher dispatcher;
    final Framework framework;
    final List<Sprite> sprites;

    GameDriver(Framework framework) {
        framework.addEventListener(self);
        self.framework = framework;
        sprites = new ArrayList<>();
        dispatcher = new EventDispatcher(sprites);
    }

    public static void main(String[] args) {
        // register for events form your framework
        Framework f = new Framework(); // or window or whatever
        new GameDriver(f).startGame(Integer.parseInt(args[0]));
    }

    void startGame(int players) {
        // initialize game state
        for (int player = 0; player <= players; player++) {
            Sprite s = new Sprite(player);
            sprites.add(s);
            s.start();
        }
        // and your event processing thread
        dispatcher.start();

        // loop forever
        framework.processEvents();
    }

    @Override
    void onEvent(Event e) {
        if (e == Events.Quit) {
            dispatcher.interrupt();
        } eles {
            dispatcher.q.put(e);
        }
    }
}

class EventDispatcher extends Thread implements Runnable {
    // setup a queue for events
    final Queue<Event> q;
    final List<Sprite> sprites;

    EventDispatcher(List<Sprite> sprites) {
        super(this, "Event Dispatcher");
        this.sprites = sprites;
        q = new BlockingQueue<>();
    }

    @Override
    void run() {
        while (!interrupted()) {
            Event e = q.take();
            getSpriteForEvent(e).q.put(e);
        }
        for (Sprite s : sprites) {
            s.interrupt();
        }
    }
}

class Sprite extends Thread implements Runnable {
    final int num;
    final Queue<Event> q;

    Sprite(int num) {
        super(this, "Sprite " + num);
        self.num = num;
        q = new BlockingQueue<>();
    }

    @Override
    void run() {
        while (!interrupted()) {
            Event e = q.take();
            handle(e);
        }
    }

    void handle(Event e) {
        // remember we assumed this takes a really long time..
        // but how do I know how to calculate anything?
        switch (e) {
            case Events.UP:
                   // try to do something really long...
                   waitForUpvoteOn("a/35911559/1254812");
               break; // (;
            ...
        }
    }
}

Now you have new problems to solve. Your game needs a clock. Events need to be batched into time windows which may or may not correlate directly to frames. What happens when one event comes in and a sprite is still processing an old event? Will you cancel processing of the old event or will you drop a frame? You also have to manage the size of your queues--can't produce more events than you can consume.

现在你有新问题需要解决。你的游戏需要一个时钟。需要将事件分组到时间窗口中,这些窗口可能会或可能不会直接与帧相关联。当一个事件进来并且精灵仍在处理旧事件时会发生什么?你会取消旧活动的处理还是会丢帧?您还必须管理队列的大小 - 不能产生比您可以消耗的更多事件。

The point is there has to be a source of truth for determining game state. A referee.. if you will. Turns out it's usually easiest to process all your events on a single thread. Think about it, if each sprite/thread has a ref then they still have to synchronize their individual world views. This effectively serializes the processing of game logic.

关键是必须有确定游戏状态的真相来源。裁判..如果你愿意的话。事实证明,在单个线程上处理所有事件通常最简单。想一想,如果每个sprite / thread都有一个ref,那么他们仍然需要同步他们各自的世界观。这有效地序列化了游戏逻辑的处理。

Let's add the timer and drawing:

让我们添加计时器和绘图:

class GameDriver ... {
    static final DELTA = 10; // ms
    final Timer timer;
    ...
    GameDriver(...) {
        ...
        timer = new Timer(dispatcher, DELTA);
        dispatcher = new EventDispatcher(sprites, f.canvas(), map);
    }

    void startGame(...) {
        ...
        // and your event processing thread and timer
        dispatcher.start();
        timer.start();   
        ...
    }

    @Override
    void onEvent(Event e) {
        if (e == Events.Quit) {
            timer.stop();
            dispatcher.interrupt();
        } else {
            if (!dispatcher.q.offer(e)) {
                // Oh no! We're getting more events than we can handle.
                // To avoid getting into this situation you can try to:
                //   1. de-dupe/coalesce/buffer events
                //   2. increase your tick interval (decrease frame rate)
                //   3. drop events (I shot you I swear!)
            }
        }
    }
}

class EventDispatcher ... {
    // setup a queue for events
    final Queue<Event> q;
    final Canvas canvas;

    EventDispatcher(List<Sprite> sprites, Canvas c) {
        super(this, "Event Dispatcher");
        q = new BlockingQueue<>();
        canvas = c;
    }

    @Override
    void tick() {
        for (Sprite s : sprites) {
            canvas.push();
            s.draw(canvas);
            canvas.pop();
        }
    }
}

class Sprite ... implements Drawable ... {
    final Bitmap bitmap;
    final Matrix matrix;
    ...
    Sprite(int num) {
        ...
        URL url = Sprite.class.getResource("sprites/player-"+num+".bmp");
        bitmap = new Bitmap(url);
        matrix = new Matrix();
    }

    @Override
    void draw(Canvas c) {
        c.apply(matrix);
        c.paint(bitmap);
    }

    void handle(Event e) {
        switch (e) {
            case Events.Left:
               matrix.translate(GameDriver.DELTA, 0);
               break;
            case Events.Down:
               matrix.translate(0, GameDriver.DELTA);
            ...
        }
    }
}

And after you cut out the useless threads for each sprite:

在你为每个精灵切掉无用的线程之后:

class GameDriver ... {
    void startGame(...) {
        // don't need to start() the sprites anymore..
        ...
            // give sprites a starting position
            sprite.matrix.translate(0, player);
    }
}

class EventDispatcher extends Thread implements Runnable {

    final Map<Matrix, Sprite> map;
    ...
    EventDispatcher(...) {
        ...
        map = new HashMap<>();
    }

    ...
    @Override
    void tick() {
        for (Sprite s : sprites) {
            // assuming we gave matrix a map-unique hash function
            checkBounds(s);
            map.put(s.matrix, s);
        }
        // process collisions or otherwise apply game logic
        applyLogic(map);
        map.clear();

        // draw the sprites (or use yet another thread)
        for (Sprite s : sprites) {
            canvas.push();
            s.draw(canvas);
            canvas.pop();
        }
    }

    @Override
    void run() {
        try {
            while (!interrupted()) {
                Event e = q.take();
                getSpriteForEvent(e).handle(e)
            }
        } catch (InterruptedException e) {
        } finally {
            for (Sprite s : sprites) {
                s.interrupt();
            }
        }
    }
    ...
}

class Sprite implements Drawable {
    ...
    // scratch the run method and thread constructor
    ...
}

I don't write games so I probably got something wrong..

我不写游戏,所以我可能出了点问题。


Anyway, there are a few takeaways. Recall that your computer has a fixed number of cores. Any number of threads greater than the number of cores will mean you have to switch context between threads. One thread is paused, its registers and stack saved, and a new thread is loaded. This is what your operating system's scheduler does (if it supports threads, as most do).

无论如何,有一些外卖。回想一下,您的计算机具有固定数量的核心。任何数量的线程大于核心数将意味着您必须在线程之间切换上下文。暂停一个线程,保存其寄存器和堆栈,并加载新线程。这是您的操作系统的调度程序所做的事情(如果它支持线程,就像大多数一样)。

So an unbounded number of sprites and/or other game objects each backed by it's own thread is just about the worst design you can imagine. It can really drop your tick rate.

所以一个无限数量的精灵和/或其他游戏对象,每个都由它自己的线程支持,这是你能想象到的最糟糕的设计。它可以真正降低你的滴答率。

Second, as I've already alluded to, in order to avoid race conditions (game checks position of sprite whilst sprite's thread is in the middle of updating it) you have to synchronize access to sprites data, anyway. If you can't compute your logic within one tick on one thread, perhaps you can explore having a work queue to perform sprite updates. But not one thread per sprite.

其次,正如我已经提到的那样,为了避免竞争条件(游戏检查sprite的位置,而sprite的线程正在更新它的中间),你必须同步访问sprite数据。如果您无法在一个线程上的一个刻度内计算逻辑,那么您可以探索具有工作队列来执行精灵更新。但每个精灵不是一个线程。

This is why, as suggested in a comment, 3 threads is a good ballpark. One to interface with the OS. One to process game logic. One to render graphics. (Leaving room for your GC thread if you're using Java.)

这就是为什么,如评论中所建议的,3个线程是一个很好的球场。一个与操作系统接口。一个来处理游戏逻辑。一个渲染图形。 (如果您使用的是Java,则为GC线程留出空间。)

Another way to think of it is that your job is to find the smallest window in which you can process input, resolve game state, and emit rendering events for the entire game. Then, just repeat this over and over again. The smaller your window the smoother your game and the higher your frame rate.

另一种思考方式是你的工作是找到最小的窗口,你可以在其中处理输入,解决游戏状态,并为整个游戏发出渲染事件。然后,一遍又一遍地重复这个。窗口越小,游戏越平滑,帧速率越高。


Finally, I should mention that the model you seek does actually arise in real world game design but for other reasons. Imagine a multiplayer game with many clients and a server (each effectively representing a new thread of execution). Each client will process its own input events, batch them, translate them to game events, and feed the game events into a server. The network becomes your serialization layer. The server will slurp up events, resolve game state, and send a reply back to the clients. The clients will accept the reply, update their local state, and render it. But the server sure as hell isn't going to wait more than a few frames for slow clients.

最后,我应该提一下,你所寻求的模型确实出现在现实世界的游戏设计中,但出于其他原因。想象一下拥有许多客户端和服务器的多人游戏(每个都有效地代表一个新的执行线程)。每个客户端将处理自己的输入事件,批量处理,将其转换为游戏事件,并将游戏事件提供给服务器。网络成为您的序列化层。服务器将淹没事件,解决游戏状态,并将回复发送回客户端。客户端将接受回复,更新其本地状态并进行呈现。但是服务器肯定不会为慢速客户端等待几帧。

Lag sux, my friend.

Lag sux,我的朋友。