我正在使用 Cocoa 在 Objective C++ 中为 Mac 开发一个 OpenGL 4.1 游戏引擎,没有 nib 文件。我发现在极少数情况下(可能是十五分之一?),通过在扩展坞中选择“退出”来退出游戏会触发 SIGTERM 信号,并且not 在 NSApplication 上调用
terminate:
。该应用程序从扩展坞和应用程序切换器中消失,但该应用程序作为不可见进程保持打开状态,必须从活动监视器终止(游戏循环继续运行,无法与程序交互)。
发生这种情况时,Xcode 调试器会在我的渲染代码中的这一行中断:
[[view openGLContext] flushBuffer];
编辑:事实证明,这实际上可以在任何执行点发生,但最常发生在这次调用期间,因为我的游戏引擎大部分时间都在等待flushBuffer返回。 /编辑
...使用 SIGTERM。 “view”是与我的 NSWindow 关联的 NSOpenGLView。 Xcode 终端或控制台中没有出现关联的错误消息。单击“继续”,然后将进程放回到游戏循环中。
SIGTERM 是 macOS 处理从 Dock 退出的预期方式吗?我找不到任何在线资源告诉我需要一个信号处理程序来处理有效的退出请求。
NSSupportsSuddenTermination
已关闭,我无法识别任何其他可能导致收到 SIGTERM 信号而不是常规 terminate:
调用的因素。我也不清楚这与一直是断点的 flushBuffer
调用有什么关系(如果有的话)。
编辑:这是主要功能,删除了不相关的特定于引擎的内容:
int main(int argc, const char * argv[])
{
@autoreleasepool
{
// Create the NSApplication
[NSApplication sharedApplication];
// Set up custom app delegate
MacAppDelegate * delegate = [[MacAppDelegate alloc] init];
[NSApp setDelegate:delegate];
// Activate and launch NSApp
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
[NSApp setPresentationOptions:NSApplicationPresentationDefault];
[NSApp activateIgnoringOtherApps:YES];
[NSApp finishLaunching];
{
Application app; // Pure C++ class, destructor called when leaving scope
NSEvent *e;
do
{
while((e = [NSApp nextEventMatchingMask: NSEventMaskAny
untilDate: nil
inMode: NSDefaultRunLoopMode
dequeue: YES]))
{
// Pump messages
[NSApp sendEvent: e];
[NSApp updateWindows];
}
} while (app.Run()); // Returns false when the player has quit
}
}
return EXIT_SUCCESS;
编辑2:我已经确认这个问题与OpenGL无关,因为我已经用一个不创建OpenGL上下文的准系统Cocoa项目重现了这个问题。如果有人想尝试测试它,可以从这里的 GitHub 下载该项目:https://github.com/theOtherMichael/SimpleCppGameLoopForMac
我想我已经弄清楚了这里发生的一些事情。我遇到了类似的问题,但就我而言,几乎 95% 的情况都是在尝试从 Dock 退出时发生
在 kAEQuitApplication 处理程序有机会被处理之前,调试器似乎在 SIGTERM 处停止。这感觉非常像 Cocoa 中的竞争条件,我怀疑 NIB 领域中的某些东西可以优雅地处理这个问题(并且可能还阻止调试器在这种情况下在 SIGTRAP 上中断)
我通过以下步骤取得了一些成功:
static void sigTermHandler (int signum)
{
// Trap the signal, tell the application to shutdown
[application terminate:nil];
}
// Wherever your entry point is
int main(int argc, const char * argv[])
{
// Set the signal handler
if (signal(SIGTERM, sigTermHandler) == SIG_ERR)
{
// error
}
...
...
}
(lldb) pro hand -p true -s false SIGTERM
NAME PASS STOP NOTIFY
=========== ===== ===== ======
SIGTERM true false true
请参阅 here 了解有关该内容的更多信息(那里的注释描述了执行此操作的其他方法,例如:添加断点操作)
这一切都非常令人讨厌,并且您面临竞争条件仍然存在的风险 - 您最终可能会在发生关闭的情况下干净地运行并且此信号处理程序然后也尝试执行关闭,因此您可能需要保护为此(事实上,绝对如此)
我对此并不是非常满意,但这是我迄今为止所设法解决的问题,它至少确保了在调试器之外正确关闭(并且在调试器内只有轻微的不便)