且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

Sprite Kit中可重用的多线程实现

更新时间:2023-02-02 10:08:15

如果您有需要的工作在每一帧都做,并且需要在渲染帧之前完成,多线程可能无法帮助你,除非你愿意付出很多努力。

If you have work that you need to do on every frame, and that needs to get done before the frame is rendered, multithreading probably won't help you, unless you're willing to put a lot of effort into it.

维持帧速率几乎是时间 - 而不是CPU资源,只是暂停时间。为了保持60fps帧速率,你有16.67毫秒来完成所有工作。(实际上,不到那个,因为SpriteKit和OpenGL需要一些时间来渲染你的工作结果。)这是一个同步问题 - 你有工作,你有一定的时间来完成它,所以提高性能的第一步是减少工作量或提高效率。

Maintaining a frame rate is all about time — not CPU resources, just wall time. To keep a 60fps framerate, you have 16.67 ms to do all your work in. (Actually, less than that, because SpriteKit and OpenGL need some of that time to render the results of your work.) This is a synchronous problem — you have work, you have a specific amount of time to do it in, so the first step to improving performance is to do less work or do it more efficiently.

多线程,打开另一方面,通常用于异步问题 - 你需要做的工作,但它不需要立即完成,所以你可以继续做你需要做的其他事情现在(就像在16毫秒内从你的更新方法返回以保持你的帧率)并稍后检查该工作的结果(比如,在稍后的帧上)。

Multithreading, on the other hand, is generally for asynchronous problems — there's work you need to do, but it doesn't need to get done right now, so you can get on with the other things you need to do right now (like returning from your update method within 16 ms to keep your framerate up) and check back for the results of that work later (say, on a later frame).

但这两个定义之间存在一些摆动空间:几乎所有现代iOS设备都有多核CPU因此,如果你正确地玩牌,你可以通过并行化你的工作量来适应你的同步问题中的一些异步性。完成这项工作并做得很好,这不是一件小事 - 它一直是的主题。多年来,大型游戏工作室进行了认真的研究和投资。

There is a little bit of wiggle room between these two definitions, though: just about all modern iOS devices have multicore CPUs, so if you play your cards right you can fit a little bit of asynchronicity into your synchronous problem by parallelizing your workload. Getting this done, and doing it well, is no small feat — it's been the subject of serious research and investment by big game studios for years.

看一下场景如何处理动画帧在SpriteKit编程指南中。那是你的16毫秒时钟。浅蓝色区域是Apple的SpriteKit(以及OpenGL和其他系统框架)代码负责的16 ms的片段。其他片是你的。让我们展开该图表以获得更好的外观:

Take a look at the figure under "How a Scene Processes Frames of Animation" in the SpriteKit Programming Guide. That's your 16 ms clock. The light blue regions are slices of that 16 ms that Apple's SpriteKit (and OpenGL, and other system frameworks) code is responsible for. The other slices are yours. Let's unroll that diagram for a better look:

如果你在任何一个切片中做了太多工作,或者使SpriteKit的工作量太大,整个事情就会超过16毫秒并且你的帧率下降。

If you do too much work in any of those slices, or make SpriteKit's workload too large, the whole thing gets bigger than 16 ms and your framerate drops.

线程化的机会是在同一时间线内在其他CPU上完成一些工作。如果SpriteKit对动作,物理和约束的处理不依赖于那些工作,你可以与这些工作同时进行:

The opportunity for threading is to get some work done on the other CPU during that same timeline. If SpriteKit's handling of actions, physics, and constraints doesn't depend on that work, you can do it in parallel with those things:

或者,如果你的工作需要在之前发生SpriteKit运行动作&物理,但你需要在更新方法中完成其他工作,你可以将一些工作发送到另一个线程,同时完成其余的更新工作,然后在更新方法中检查结果:

Or, if your work needs to happen before SpriteKit runs actions & physics, but you have other work you need to do in the update method, you can send some of the work off to another thread while doing the rest of your update work, then check for results while still in your update method:

那么如何完成这些事情呢?这是使用调度组的一种方法,并假设动作/物理/约束不依赖于您的后台工作 - 它完全偏离我的头脑,所以它可能不是***的。 :)

So how to accomplish these things? Here's one approach using dispatch groups and the assumption that actions/physics/constraints don't depend on your background work — it's totally off the top of my head, so it may not be the best. :)

// in setup
dispatch_queue_t workQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t resultCatchingGroup = dispatch_group_create();
id stuffThatGetsMadeInTheBackground;

- (void)update:(NSTimeInterval)currentTime {
    dispatch_group_async(group, queue, ^{
        // Do the background work 
        stuffThatGetsMadeInTheBackground = // ...
    });
    // Do anything else you need to before actions/physics/constraints
}

- (void)didFinishUpdate {
    // wait for results from the background work
    dispatch_group_wait(resultCatchingGroup, DISPATCH_TIME_FOREVER);

    // use those results
    [self doSomethingWith:stuffThatGetsMadeInTheBackground];
}

当然, dispatch_group_wait 顾名思义,阻止执行等待你的后台工作完成,所以你仍然有16ms的时间限制。如果前台工作(你的更新的其余部分,加上SpriteKit的动作/物理/约束工作以及你为响应这些事情而完成的任何其他工作)之前完成你的背景工作确实如此,你会等待它。如果后台工作加上SpriteKit的渲染工作(以及在产生后台工作之前你在更新中做的任何事情)花费的时间超过16毫秒,你仍然会丢帧。因此,解决这个问题的方法是充分了解您的工作量,以便安排好。

Of course, dispatch_group_wait will, as its name suggests, block execution to wait until your background work is done, so you still have that 16ms time constraint. If the foreground work (the rest of your update, plus SpriteKit's actions/physics/constraints work and any of your other work that gets done in response to those things) gets done before your background work does, you'll be waiting for it. And if the background work plus SpriteKit's rendering work (plus whatever you do in update before spawning the background work) takes longer than 16 ms, you'll still drop frames. So the trick to this is knowing your workload in enough detail to schedule it well.