12

我正在尝试将游戏库移植到 iPhone。与 SDL 不同,此库不会完全控制您的 main() 函数,而是通过您自己的代码中的快速返回函数与它进行通信。因此,例如,明显的伪代码:

int main() {
  library_init();
  // game init code here
  while(we_have_not_quit_the_game) {
    library_message_loop();
    library_init_render();
    // render stuff
    library_end_render();
    // update game state
  }
  library_shutdown();
}

iPhone 使这变得困难,因为它要求您调用一个永远不会返回的 UIApplicationMain 函数。在 library_init(); 之后,我根本无法回到用户代码。

我不相信这是必要的——据说有 NSRunLoop 可以用来处理事件。但是,我不知道 UIApplicationMain 是否还有其他重要的功能。(请注意,我没有使用 .nib 文件的计划,这是我发现 UIApplicationMain 所做的唯一其他事情。)

我有三个我能想到的真实想法,但它们都是主要的实施工作,所以我想知道是否有人有这方面的经验,然后再花一天时间尝试注定失败的想法。

  • 在 Init 中,生成一个新线程,在该线程中运行 UIApplicationMain。要么跨线程通信所有事件(呃),要么只是让 UIApplicationMain 线程进入睡眠状态并在主线程中使用 CFRunLoop。但是,我听说 UIApplicationMain 不喜欢在不同的线程中运行。
  • 完全忽略 UIApplicationMain,只使用 NSRunLoop。我会错过重要的 iPhone 设置吗?谁知道!
  • 使用 longjmp() 做一些可怕的事情以在设置后跳出 UIApplicationMain 代码,祈祷它在拆卸期间不会做任何重要的事情。

建议?

4

4 回答 4

5

看起来我在这里回答我自己的问题!在我能够在真实硬件上对其进行测试并将其放入应用商店之前,我不会接受我的答案。也就是说,我将在此处保留我的最新信息,包括哪些选项不起作用。

想法 #1:事实证明,每个 NSRunLoop 都是线程特定的。如果我在单独的线程中创建 UIApplicationMain,它不会收到任何消息。作为副作用,这使得无法确定它何时完成初始化,所以如果它有任何非线程安全的东西,它就不会工作。我也许可以跨线程向它发送一条消息,以确定它何时完成初始化,但现在我称之为死胡同。

想法#2:UIApplicationMain 做了很多微妙的事情。我不确定它的限制是什么,但是如果不涉及 UIApplicationMain,我就无法进行任何工作。想法#2是正确的。

想法#3:接收操作系统信号很重要——你需要知道是否有电话覆盖,或者你是否即将退出。最重要的是,为了正确启动应用程序,一些设置消息似乎至关重要。如果不在 UIApplicationMain 中,我找不到任何方法来保持发送消息。我想出的唯一选择是 NSRunLoop 和 CFRunLoop。两者都不起作用 - 消息没有像我想要的那样进来。我可能没有正确使用这些,但无论如何,想法#3 已经过时了。

全新的疯狂想法#4:可以使用 setjmp/longjmp 来伪造 C/C++ 中的协程。诀窍是首先将堆栈指针设置为不会破坏任何重要内容的某个值,然后启动第二个例程,然后来回跳转,假装你有两个堆栈。如果你的“第二个协程”决定从它的主函数返回,事情会变得有点混乱,但幸运的是, UIApplicationMain 永远不会返回,所以这不是问题。

我不知道是否有办法在真实硬件上显式设置堆栈指针,例如,设置为我动态分配的一大块数据。幸运的是,这没关系。iPhone 默认有一个 1MB 的堆栈,这很容易容纳一些协程。

我目前正在做的是使用 alloca() 将堆栈指针向前推 768 KB,然后生成 UIApplicationMain,然后使用 setjmp/longjmp 在我的“UI 例程”和“主例程”之间来回切换。到目前为止,这是有效的。

注意事项:

  • 不可能知道“UI 例程”什么时候没有消息要处理,什么时候没有消息要处理,它会无限期地阻塞,直到不再是这种情况。我通过制作一个每 0.1 毫秒触发一次的计时器来解决这个问题。每次计时器触发时,我都会退出我的“主程序”,执行一个游戏循环,然后返回“UI 程序”进行另一个计时器滴答。阅读文档表明它不会无限期地堆叠“计时器调用”。我似乎确实适当地收到了“终止”消息,尽管我还没有设法彻底测试它,也没有测试任何其他重要消息。(幸运的是,总共只有四条消息,其中一条与设置有关。)

  • 大多数现代操作系统不会一次分配整个堆栈。iPhone可能就是其中之一。我不知道的是,可以这么说,将堆栈指针撞到 3/4 的兆前是否会分配“后面”的所有内容。如果是这样,我可能实际上浪费了 3/4 兆内存,这在 iPhone 上非常重要。这可以通过将指针向前移动一个较小的量来处理,但这确实是在招致堆栈大小的灾难——它有效地限制了你的堆栈,无论你将指针向前移动多远,你必须提前弄清楚这一点。堆栈中的一些哨兵数据,加上良好的监控和堆栈大小问题的日志系统,可能可以解决这个问题,但这是一个不平凡的问题。(或者,如果我能弄清楚如何直接在本机硬件上使用堆栈指针,我可以只用 malloc()/new[] 几千字节,将堆栈指针指向它,然后将它用作我的新堆栈。我必须弄清楚它需要多少空间,但考虑到它并没有做太多,我怀疑它会很多。)

  • 目前尚未在实际硬件上进行测试(给它一两个星期,我还有另一个项目要先完成。)

  • 我不知道当我尝试提交到应用商店时,Apple 是否会弄清楚我在做什么并在其上贴上一个巨大的 REJECTED 贴纸。容我们说,这稍微超出了他们对 API 的意图。手指交叉。

我会不断更新这篇文章,并在我确认它有效后正式接受它。

延迟更新:我被其他各种事情分心了。从那以后,我发生了一些变化,使我对 Apple 开发的兴趣大大降低。我目前的方法没有显示出不起作用的迹象,但我真的没有动力继续充实它。对不起!如果我改变主意,我会进一步更新,但 Outlook 不太好。

于 2010-02-06T05:55:03.880 回答
1

我知道NSApplicationMain()读取 Info.plist 并对应用程序做一些事情(比如获取初始 nib 文件),所以我猜这UIApplicationMain()对 iPhone 也是如此(比如获取初始状态栏样式、缩放 default.png 图像等)。这些东西不会暴露在其他任何地方,所以它调用的函数仍然需要运行才能让应用程序启动而没有任何副作用。您唯一的赌注是对它们进行逆向工程并复制它们(并希望他们所做的任何事情都在公共 SDK 中)。

于 2010-02-24T03:43:52.433 回答
0

为什么不从 UIApplication 开始你的游戏循环呢?(是的,你不能把它放进去,main()但这不重要。)一些代表消息是很好的候选人。

于 2010-02-03T04:52:42.533 回答
0

目标是采用 main() 函数并让它在 iPhone 上不加修改地工作吗?

看起来你不可能完全让库的用户不考虑 iPhone 平台——他们将不得不处理 XCode 来进行代码签名之类的事情。

鉴于此,告诉用户他们必须将他们的 main() 函数分成几部分,您可以从 applicationDidFinishLaunching 和适当的计时器调用这些部分,这似乎不会给任何人带来太多不便。

于 2010-02-03T00:35:46.750 回答