Cocoa:将NSApplication集成到现有的c ++ mainloop中

时间:2011-07-18 11:45:56

标签: c++ cocoa integration nsapplication

我知道,我不是第一个尝试在OSX上使用Cocoa和现有的c / c ++主循环的人,但我并不是真的喜欢我到目前为止遇到的解决方案所以我想出了一个不同的我想讨论的想法。我发现的最常见的方式(在glut,glfw,SDL和QT中我认为)是使用轮询来替换NSApplications run方法并自己处理事件:

nextEventMatchingMask:untilDate:inMode:dequeue:

这有一个很大的缺点,即cpu永远不会真正空闲,因为你必须轮询整个时间以检查是否有任何新事件,而且它不是NSApplications内部唯一的事情运行函数,所以它可能会破坏一些如果您使用此替代品,请参阅详细信息。

所以我想做的是保持cocoa runLoop完好无损。想象一下,你有自己的计时器方法在c ++中实现,通常会在你的主循环中进行管理和触发(这只是一个小部分作为例子)。我的想法是将所有循环部分移动到辅助线程(因为据我所知,NSApplication运行需要从主线程调用),然后将自定义事件发布到NSApplication的派生版本,该版本在其内部适当地处理它们sendEvent:方法。例如,如果我的计时器在我的c ++循环测量中测量,我会向NSApplication发布一个自定义事件,然后运行我的应用程序的loopFunc()函数(也驻留在mainthread中),它适当地将事件发送到我的c ++事件链中。 首先,您认为这是一个很好的解决方案吗? 如果是的话,你将如何在cocoa中实现它,我只在NSEvent Reference中发现了这个方法来发布自定义的NSApplicationDefined事件:

otherEventWithType:location:modifierFlags:timestamp:windowNumber:context:subtype:data1:data2:

然后使用类似的东西:

[NSApp postEvent:atStart:]

通知NSApplication。

我宁愿发布没有任何窗口信息的事件(在otherEventWithType中),我可以简单地忽略那部分吗?

然后我想象覆盖NSApplications的sendEvent函数类似于:

    - (void)sendEvent:(NSEvent *)event
{
    //this is my custom event that simply tells NSApplication 
    //that my app needs an update
    if( [event type] == NSApplicationDefined)
    {
        myCppAppPtr->loopFunc(); //only iterates once
    }
        //transform cocoa events into my own input events
    else if( [event type] == NSLeftMouseDown)
    {
             ...
             myCppAppPtr->loopFunc(); //also run the loopFunc to propagate input events
    }
        ...

    //dont break the cocoa event chain
    [super sendEvent:event];

}

对不起这篇长篇文章感到遗憾,但这一直困扰着我,因为到目前为止我对这个主题的发现并不满意。这是我在NSApplication中发布和检查自定义事件的方式,您是否认为这是将cocoa集成到现有runloop而不进行轮询的有效方法?

1 个答案:

答案 0 :(得分:26)

好的,毕竟这比我想象的要花费更多时间,我想概述我尝试过的事情并告诉你我和他们有什么经历。这将有望挽救那些试图在未来将Cocoa整合到现有主循环中的人们。我在搜索讨论的问题时找到的第一个函数是函数

nextEventMatchingMask:untilDate:inMode:dequeue:

但正如我在问题中所说的,我的主要问题是我不得不经常轮询新事件,这会浪费相当多的CPU时间。 所以我尝试了以下两种方法来简单地让我的mainloops更新函数从NSApplications mainloop调用:

  1. 将自定义事件发布到NSApplication ,覆盖NSApplications sendEvent:函数,只需调用我的mainloops更新函数 从那里。与此类似:

    NSEvent* event = [NSEvent otherEventWithType: NSApplicationDefined
                                                         location: NSMakePoint(0,0)
                                                   modifierFlags: 0
                                                       timestamp: 0.0
                                                    windowNumber: 0
                                                         context: nil
                                                         subtype: 0
                                                           data1: 0
                                                           data2: 0];
                    [NSApp postEvent: event atStart: YES];
    
    
    //the send event function of my overwritten NSApplication
       - (void)sendEvent:(NSEvent *)event
    {
        //this is my custom event that simply tells NSApplication 
        //that my app needs an update
        if( [event type] == NSApplicationDefined)
        {
            myCppAppPtr->loopFunc(); //only iterates once
        }
    }
    

    这在理论上只是一个好主意,因为如果我的应用程序更新了 快速(例如由于计时器快速点火),整个 可可事件队列变得没有响应,因为我添加了这样 许多自定义事件。 所以不要使用这个......

  2. 将performSelectorOnMainThread 与其中的cocoaFunction一起使用 转来调用我的更新功能

    [theAppNotifier
    performSelectorOnMainThread:@selector(runMyMainLoop) withObject:nil
    waitUntilDone:NO ];
    

    这很好,app和cocoa EventLoop非常好 响应。如果你只是想要实现一些简单的事情我会 建议沿着这条路走,因为这是最简单的路线 这里提出。无论如何,我对命令的控制很少 这种方法发生的事情(如果你有一个,这是至关重要的 多线程应用程序),即当我的计时器开除并且会做一个相当的 长期工作,通常他们会在任何新的工作之前重新安排 鼠标/键盘输入可以添加到我的eventQueue,因此会 使整个输入缓慢。在窗口上打开垂直同步 由重复计时器绘制的就足以让这种情况发生。

  3. 毕竟我不得不回到nextEventMatchingMask:untilDate:inMode:dequeue:并经过一些试验和错误后,我实际上找到了一种方法,可以让它在没有持续轮询的情况下工作。我的循环结构类似于:

    void MyApp::loopFunc()
    {
        pollEvents();
        processEventQueue();
        updateWindows();
        idle();
    }
    

    其中pollEvents和idle是重要的函数,基本上我使用类似的东西。

    void MyApp::pollEvents()
    {
        NSEvent * event;
    
        do
        {
            event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate distantPast] inMode:NSDefaultRunLoopMode dequeue:YES];
    
    
                //Convert the cocoa events to something useful here and add them to your own event queue
    
            [NSApp sendEvent: event];
        }
        while(event != nil);
    }
    

    为了在idle()函数中实现阻塞,我做了这个(不确定这是不是很好,但它看起来效果很好!):

    void MyApp::idle()
    {
        m_bIsIdle = true;
        NSEvent * event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate distantFuture] inMode:NSDefaultRunLoopMode dequeue:NO];
        m_bIsIdle = false;
    }
    

    这会导致cocoa等到有事件发生,如果发生空闲则只是退出并且loopfunc再次启动。要唤醒闲置功能,如果我的一个计时器(我不使用可可计时器)触发,我再次使用自定义事件:

    void MyApp::wakeUp()
    {
        m_bIsIdle = false;
    
        //this makes sure we wake up cocoas run loop
        NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
        NSEvent* event = [NSEvent otherEventWithType: NSApplicationDefined
                                            location: NSMakePoint(0,0)
                                       modifierFlags: 0
                                           timestamp: 0.0
                                        windowNumber: 0
                                             context: nil
                                             subtype: 0
                                               data1: 0
                                               data2: 0];
        [NSApp postEvent: event atStart: YES];
        [pool release];
    }
    

    由于我之后清除了整个可可事件队列,因此我没有第1部分中描述的相同问题。 但是,这种方法也存在一些缺点,因为我认为它并没有完成[NSApplication run]内部所做的所有事情,即应用程序代理这样的事情:

    - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)theApplication
    {
          return YES;
    }
    

    似乎没有用,反正我可以忍受,因为如果最后一个窗口刚关闭,你可以轻松检查自己。

  4. 我知道这个答案非常冗长,但我到达那里的旅程也是如此。我希望这可以帮助某人并防止人们犯下我所犯的错误。