Sprite Kit中可重复使用的multithreading实现

我正在做一个Sprite Kit游戏,我需要做一些multithreading来保持健康的fps。

在更新时,我调用一个函数来创build大量的UIBezierPaths,并使用C ++静态库合并它们。

如果我有超过10个形状的帧速率急剧下降,所以我决定给GCD尝试并尝试用单独的线程来解决这个问题。

我把这个放在didMoveToView:

queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0); 

并在每帧被调用的函数中,我称之为:

 dispatch_async(queue,^(void){[self heavyCalculationsFunc];}); 

对于那些了解GCD的人来说,这可能是显而易见的,它会在每个框架上创build一个新的线索,但是对于我来说还不清楚。

我的问题是,是否有任何方法来重新使用我想调用更新的线程?

感谢您的帮助提前!

如果你在每一帧都需要完成的工作,并且需要在渲染框架之前完成,multithreading可能不会帮助你,除非你愿意付出很多的努力。

维持一个帧率是所有的时间 – 不是CPU资源,只是墙壁时间。 要保持60fps的帧速率,你需要16.67毫秒的时间才能完成所有的工作(实际上,因为SpriteKit和OpenGL需要一些时间来渲染你的工作结果)。这是一个同步问题 – 你有工作,你有一个特定的时间来做到这一点,所以提高绩效的第一步是做更less的工作或更有效地做到这一点。

另一方面,multithreading通常是用于asynchronous的问题 – 你需要做的工作,但不需要马上完成,所以你可以继续做其他事情(比如返回从你的更新方法在16毫秒内保持你的帧率),稍后再检查一下这个工作的结果(比如在后面的帧)。


这两个定义之间有一点点摆动空间:几乎所有现代iOS设备都具有多核CPU,因此,如果您正确地使用卡,则可以通过并行处理工作负载,使同步问题具有一定的asynchronous性。 做好这件事,做好这件事,不是一件容易的事情 – 多年来,它一直是大型游戏工作室认真研究和投资的主题。

看一下“ SpriteKit编程指南”中的“如何处理场景处理animation帧”下的图。 这是你的16毫秒时钟。 淡蓝色区域是苹果的SpriteKit(以及OpenGL和其他系统框架)代码负责的16毫秒的片段。 其他切片是你的。 让我们展开该图,以获得更好的外观:

SpriteKit渲染循环,线性

如果你在这些切片中做了太多的工作,或者使SpriteKit的工作量太大,整个事情就会变得比16毫秒更大,帧率也会下降。

线程化的机会是在同一时间线上完成另一个CPU上的一些工作。 如果SpriteKit对动作,物理和约束的处理不依赖于这些工作,那么可以同时处理这些事情:

与SK代码并行的游戏代码

或者,如果您的工作需要在SpriteKit运行动作和物理之前发生,但是您需要在update方法中执行其他工作,则可以在执行剩余的update工作的同时将一些工作发送到另一个线程,然后检查结果仍然在update方法中:

游戏代码与自身并行


那么如何完成这些事情? 这里有一个使用调度组的方法,并假设行动/物理/约束不依赖于你的背景工作 – 这完全是我的头顶,所以它可能不是最好的。 🙂

 // 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的时间限制。 如果前台工作( update的其余部分,加上SpriteKit的动作/物理/约束条件以及任何其他工作的完成)都会在后台工作完成之前完成,那么您将需要等待。 如果后台工作加上SpriteKit的渲染工作(加上在产生后台工作之前update任何内容)花费的时间超过16毫秒,您仍然会丢帧。 所以这个诀窍是知道你的工作量足够详细,以安排好。

考虑一个稍微不同的方法。 创build和维护自己的队列,而不是获取系统队列。

a)调用dispatch_queue_create创build一个新的队列,将这个队列保存在你的对象中。 在该队列上使用dispatch_async来运行您的工作。 如果必须在下一帧之前完成,则可能需要同步

b)如果你有多个工作,考虑一个并发队列,而不是一个串行队列,根据你的依赖关系,这可能会或可能不会让事情“更快”。

有了GCD,你不应该考虑线程,如果新线程被创build/重用等等。只要想一想队列,以及你在向他们推送什么。 阅读苹果的并发编程指南和参考gcd也希望有所帮助。