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

mok*_*oka 31 c++ integration cocoa nsapplication

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

nextEventMatchingMask:untilDate:inMode:dequeue:
Run Code Online (Sandbox Code Playgroud)

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

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

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

然后使用类似的东西:

[NSApp postEvent:atStart:]
Run Code Online (Sandbox Code Playgroud)

通知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];

}
Run Code Online (Sandbox Code Playgroud)

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

mok*_*oka 27

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

nextEventMatchingMask:untilDate:inMode:dequeue:
Run Code Online (Sandbox Code Playgroud)

但正如我在问题中所说的那样,我的主要问题是我不得不经常轮询新的事件,这会浪费相当多的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
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

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

  2. 将performSelectorOnMainThread与cocoaFunction一起使用,而cocoaFunction又调用我的更新函数

    [theAppNotifier
    performSelectorOnMainThread:@selector(runMyMainLoop) withObject:nil
    waitUntilDone:NO ];
    
    Run Code Online (Sandbox Code Playgroud)

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

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

    void MyApp::loopFunc()
    {
        pollEvents();
        processEventQueue();
        updateWindows();
        idle();
    }
    
    Run Code Online (Sandbox Code Playgroud)

    其中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);
    }
    
    Run Code Online (Sandbox Code Playgroud)

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

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

    这导致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];
    }
    
    Run Code Online (Sandbox Code Playgroud)

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

    - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)theApplication
    {
          return YES;
    }
    
    Run Code Online (Sandbox Code Playgroud)

    似乎没有工作,反正我可以忍受,因为你可以很容易地检查自己是否最后一个窗口关闭.

我知道这个答案很长,但我去那儿的旅程也是如此.我希望这可以帮助某人并防止人们犯下我所犯的错误.