【问题标题】:iOS Games and Run-Loop ManagementiOS 游戏和运行循环管理
【发布时间】:2011-01-27 15:03:46
【问题描述】:

首先,我的问题是:您如何管理您的 iOS 运行循环?

接下来是我的原因:我一直在研究各种原型(v. 早期开发)并发现了许多令人困惑的问题。

  • 首先,输入问题和运行循环引导我尝试以下操作:
    • 在使用最推荐的系统 (CADisplayLink) 时,我注意到一旦 CPU 负载导致缓冲区翻转 (presentRenderBuffer) 必须等待一帧,某些触摸输入就会丢失。这仅发生在设备上而不发生在模拟器中(令人讨厌 - 这似乎与等待主线程上的 vsync 阻塞以及应用运行循环处理触摸输入和吃消息的方式有关)
    • 在使用下一个最推荐的系统 (NSTimer) 时,我注意到一旦 CPU 负载达到模拟器中的某个点而不是设备中的某个点,某些触摸输入就会丢失(也很烦人)。 NSTimer 也会导致我的更新触发时的精度要低得多
    • 当使用最不推荐的系统时(在它自己的线程中运行运行循环,并使用从 mach_absolute_time 构建的高精度计时器进行内部管理,我所有的触摸输入问题都消失了,但是我的 ASSERT 代码现在陷入了错误的线程中,并且仅如果我在软件中断后睡着了。(我的断言代码类似于http://iphone.m20.nl/wp/?p=1)我真的很喜欢让我的断言代码立即陷入导致问题的行,所以这个解决方案对我来说并不可行:更难调试。
  • 二、浪费时间:
    • 在调查系统时,我发现无论帧率如何(奇怪的是,但我认为在统计上它仍然有意义 w/vsync)我正在等待大约 22% 的时间在 vsync 上。我已经通过移动 glFlush/glFinish 并玩弄我执行 presentRenderBuffer 调用的频率来确认这一点。这是我喜欢处理 AI 等的关键时刻,而不是简单地停止阻塞 gl 调用。我能想到的唯一方法是将渲染移到它自己的线程中,但我不确定是否有必要开始为单处理器设备上的多线程重新架构。

那么有没有人找到解决这些问题的灵丹妙药?有没有人在这个平台上有一个杀手级的运行循环架构?目前看来我必须取其轻。

【问题讨论】:

  • 我应该注意:当我说“输入被丢弃”时,它们实际上并没有被丢弃,它们只是滞后了几分之一秒到最多 10 秒。这不是在 iOS 和其他触摸屏设备上看到的标准延迟,而更像是“消息消耗运行速度比消息生成慢”的累积延迟(随着时间的推移变得更长)
  • 你在主线程上做所有事情吗?您是否考虑过使用 GCD 或类似方法将此处理中的任何一个移至后台线程?
  • 您可能还对以下问题的讨论(包括答案​​和 cmets)感兴趣:stackoverflow.com/questions/4739748/…,其中试验了几种操作运行循环以进行 UI 更新的方法。
  • @Brad:“First”下的第 1 项和第 2 项通过 CADisplayLink 和 NSTimer 通过 obj-c run-in-main-thread-call 在主线程中。我还尝试了 2 w/out run-in-main-thread 调用。 “First”下的第 3 项在通过 mach_time 和 microsleep 管理的后台 NSThread 中运行。我没有过多地使用 GCD——我在 c++ 中比 obj-c 更舒服。感谢您的链接,起初我没有注意到任何新内容,但我会仔细研究它。
  • “如何管理运行循环”是什么意思? :-)

标签: iphone optimization ios opengl-es runloop


【解决方案1】:

对于我自己的 iOS 项目,我使用经典方法(创建一个窗口 .nib,创建一个继承 EAGLView 的类,将 EAGLView 添加到放置在其自己的 .nib 中的视图控制器中的视图中)。

在工作中,我在 SDL 的启发下采用了一种略有不同的方法,您可以在我们的开源库 APRIL 中进行检查。 APRIL 的主要目标是支持尽可能多的平台,同时保持简单性(仅限窗口和输入管理)并明确许可问题并免费使用。我们的开发人员希望在一个平台(Windows、Mac 或 Linux,根据口味和需求)编写应用程序,然后将代码交给我以适应其他平台。

在我们在 APRIL 中使用的方法中,您无需创建任何 .nib,并且在调用 UIApplicationMain 时,您将委托类指定为其第四个参数。游戏的主要代码在每个平台上都完全一样,只有特定于平台的东西会#ifdef'd 到代码中,或者抽象到帮助程序库中。

在应用委托中创建视图控制器和窗口:

- (void)applicationDidFinishLaunching:(UIApplication *)application {
    // create a window.
    // early creation so Default.png can be displayed while we're waiting for 
    // game initialization
    window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];

    // viewcontroller will automatically add imageview
    viewController = [[AprilViewController alloc] initWithWindow:window];
    [viewController loadView];

    // set window color
    [window setBackgroundColor:[UIColor blackColor]];

    // display the window
    [window makeKeyAndVisible];

    // thanks to Kyle Poole for this trick
    // also used in latest SDL
    // quote:
    // KP: using a selector gets around the "failed to launch application in time" if the startup code takes too long
    // This is easy to see if running with Valgrind

    [self performSelector:@selector(runMain:) withObject:nil afterDelay:0.2f];
}

请注意我们如何将启动延迟 0.2?这就是为什么我在上面提到图像视图。在这 0.2 秒内,我们会在 Default.png 之后立即显示空白屏幕,并且在将控制权转移到 runMain: 之前引入了额外的延迟,这会将控制权释放给主应用程序:

- (void)runMain:(id)sender
{       
    // thanks to Kyle Poole for this trick
    char *argv[] = {"april_ios"};
    int status = april_RealMain (1, argv); //gArgc, gArgv);
#pragma unused(status)
}

所以,现在控件永远不会转移回 UIApplication 的实际主循环。然后创建自己的主循环。

    void iOSWindow::enterMainLoop()
    {
            while (mRunning) 
            {
                    // parse UIKit events
                    doEvents();
                    handleDisplayAndUpdate();
            }
    }

    void iOSWindow::doEvents()
    {
            SInt32 result;
            do {
                    result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, TRUE);
            } while(result == kCFRunLoopRunHandledSource);
    }

(顺便说一句,当然,使用视图控制器来简化 UI 的旋转以匹配设备方向。)

如果操作系统支持,这两种方法都使用CADisplayLink。尽管我的私人项目主要基于加速度计,但我没有注意到这两种方法有任何问题。我怀疑 APRIL 方法也可能解决一些问题。

【讨论】:

  • 伊万真是太迷人了。事实上,您已经描述了如何实现.. 控制从 UIApplication 的实际主循环转移。然后你创建你自己的主循环 ...太棒了!太棒了!
  • 看起来您正在做的事情与我们的方式相似,即摆脱 .nib 等。不过,我发现您的“doEvents”方法很有趣,因为我的直觉告诉我我遇到的问题是事件处理。我将不得不尝试那个。我也很感兴趣你在 APRIL 是如何开始的。对于我在自己的运行循环中运行事物时的循环初始化,我只是生成了另一个线程并以这种方式管理事物,除了软件中断在错误的线程上启动之外,这非常有效。
猜你喜欢
  • 2015-05-29
  • 2012-03-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-01-21
  • 2022-01-21
  • 1970-01-01
  • 2011-12-13
相关资源
最近更新 更多