Cocoa:将 NSApplication 集成到现有的 c++主循环

发布于 2024-11-24 05:40:33 字数 1804 浏览 1 评论 0原文

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

nextEventMatchingMask:untilDate:inMode:dequeue:

这有一个很大的缺点,即 cpu 永远不会真正空闲,因为你必须一直轮询以检查是否有任何新事件,而且这并不是 NSApplications run 函数中发生的唯一事情,因此如果您使用此替换,可能会破坏一些细节。

所以我想做的就是保持可可 runLoop 完好无损。想象一下,您有自己的用 C++ 实现的计时器方法,这些方法通常在主循环内进行管理和触发(这只是示例的一小部分)。我的想法是将所有循环部分移动到辅助线程(因为据我所知,需要从主线程调用 NSApplication run),然后将自定义事件发布到我的 NSApplication 的派生版本,该版本在其内部适当地处理它们发送事件:方法。例如,如果我的计时器在我的 C++ 循环中测量,我将向 NSApplication 发布一个自定义事件,该事件依次运行我的应用程序的 LoopF​​unc() 函数(也驻留在主线程中),该函数将事件适当地沿着我的 C++ 事件链发送。 首先,您认为这会是一个好的解决方案吗? 如果是,你将如何在可可中实现它,我只在 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 集成到现有运行循环而不进行轮询的有效方法吗?

I know, that I am not the first one to try to use Cocoa on OSX together with an existing c/c++ main loop, but I am not really liking the solutions I came across so far so I came up with a different idea I'd like to discuss. The most common way I found (in glut, glfw, SDL and also QT I think) is to use polling to replace NSApplications run method and process the events yourself with this:

nextEventMatchingMask:untilDate:inMode:dequeue:

This has the big disadvantage that the cpu is never really idle since you have to poll the whole time to check if there are any new events, furthermore its not the only thing going on inside NSApplications run function, so it might break some details if you use this replacement.

So what I'd like to do is to keep the cocoa runLoop intact. Imagine you'd have your own timer methods implemented in c++ which would usually be managed and fired inside your main loop (this is just a small part as an example). My idea would be to move all my looping portions to a secondary thread (since NSApplication run needs to be called from the main thread as far as I know), and then post custom Events to my derived version of NSApplication that handles them appropriately inside its sendEvent: method. For instance if my timers measured in my c++ loop fire, I'd post a custom event to NSApplication that in turn runs loopFunc() function of my application (residing in the mainthread as well) which appropriately sends the events down my c++ event chain.
So first of all, do you think this would be a good solution?
If yes, how would you implement that in cocoa, I only found this method inside the NSEvent Reference to post custom NSApplicationDefined events:

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

and then use something like:

[NSApp postEvent:atStart:]

to notify NSApplication.

I'd rather post an event without any information about the window (in otherEventWithType), can I simply ignore that part?

Then I'd imagine to overwrite NSApplications sendEvent function similar to this:

    - (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];

}

sorry for the long post, but this has been bothering me quite a bit since I am really not happy with what I found about this subject so far. Is this how I'd post and check for a custom event inside NSApplication, and do you think this is a valid approach to integrate cocoa into an existing runloop without polling?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

羁绊已千年 2024-12-01 05:40:33

好吧,毕竟这花费了我比我预期更多的时间,我想概述一下我尝试过的事情,并告诉你我对它们的经历。这有望在未来节省人们尝试将 Cocoa 集成到现有主循环中的大量时间。我在搜索所讨论的问题时发现的第一个函数是该函数,

nextEventMatchingMask:untilDate:inMode:dequeue:

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

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

    NSEvent* 事件 = [NSEvent otherEventWithType: NSApplicationDefined
                                                         位置:NSMakePoint(0,0)
                                                   修改标志:0
                                                       时间戳:0.0
                                                    窗口数量: 0
                                                         上下文:无
                                                         亚型:0
                                                           数据1:0
                                                           数据2:0];
                    [NSApp postEvent: 事件 atStart: YES];
    
    
    //我覆盖的NSApplication的发送事件函数
       - (void)sendEvent:(NSEvent *)事件
    {
        //这是我的自定义事件,它只是告诉 NSApplication 
        //我的应用程序需要更新
        if( [事件类型] == NSApplicationDefined)
        {
            myCppAppPtr->loopFunc(); //只迭代一次
        }
    }
    

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

  2. 使用performSelectorOnMainThread与cocoaFunction,在
    Turn 调用我的更新函数

    [theAppNotifier
    PerformSelectorOnMainThread:@selector(runMyMainLoop) withObject:nil
    等待完成:否];
    

    这好多了,应用程序和可可 EventLoop 非常好
    反应灵敏。如果你只是想实现一些简单的事情我会
    建议走这条路线,因为它是最简单的路线
    在这里提出。不管怎样,我对顺序的控制很少
    使用这种方法发生的事情(如果你有一个
    多线程应用程序),即当我的计时器触发并且会做一个相当
    工作时间很长,通常他们会在任何新的工作之前重新安排时间
    鼠标/键盘输入可以添加到我的事件队列中,因此将
    使整个输入变得迟缓。在窗口上打开垂直同步
    由重复计时器绘制的值足以让这种情况发生。

  3. 毕竟我必须回到nextEventMatchingMask:untilDate:inMode:dequeue:,经过一番尝试和错误后,我实际上找到了一种无需不断轮询即可使其工作的方法。我的循环结构与此类似:

    void MyApp::loopFunc()
    {
        轮询事件();
        处理事件队列();
        更新Windows();
        闲置的();
    }
    

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

    void MyApp::pollEvents()
    {
        NSEvent * 事件;
    
        做
        {
            事件= [NSApp nextEventMatchingMask:NSAnyEventMask直到Date:[NSDate distancePast] inMode:NSDefaultRunLoopMode出队:YES];
    
    
                //将cocoa事件转换为这里有用的东西并将它们添加到您自己的事件队列中
    
            [NSApp sendEvent:事件];
        }
        while(事件!= nil);
    }
    

    为了在idle()函数中实现阻塞,我这样做了(不确定这是否好,但似乎效果很好!):

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

    这会导致 cocoa 等待,直到有事件发生,如果发生这种情况,idle 就会退出,loopfunc 会再次启动。要在我的计时器之一(我不使用可可计时器)触发时唤醒空闲功能,我再次使用自定义事件:

    void MyApp::wakeUp()
    {
        m_bIsIdle = false;
    
        //这确保我们唤醒可可运行循环
        NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
        NSEvent* 事件 = [NSEvent otherEventWithType: NSApplicationDefined
                                            位置:NSMakePoint(0,0)
                                       修改标志:0
                                           时间戳:0.0
                                        窗口数量: 0
                                             上下文:无
                                             亚型:0
                                               数据1:0
                                               数据2:0];
        [NSApp postEvent: 事件 atStart: YES];
        [矿池释放];
    }
    

    由于我随后清除了整个可可事件队列,因此我没有遇到第 1 节中描述的相同问题。
    但是,这种方法也有一些缺点,因为我认为它并没有执行 [NSApplication run] 在内部执行的所有操作,即应用程序委托如下所示:

    - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)theApplication
    {
          返回是;
    }
    

    似乎不起作用,无论如何我可以忍受这一点,因为你可以轻松地检查自己是否最后一个窗口刚刚关闭。

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

okay, after all this took me way more time than I expected, and I'd like to outline the things I tried and tell you what experiences I had with them. This will hopefully save people trying to integrate Cocoa into an existing mainloop alot of time in the future. The first function I found when searching for the discussed matter was the function

nextEventMatchingMask:untilDate:inMode:dequeue:

but as I said in the question, my main problem with this was that I would have to constantly poll for new events which would waste quite some CPU Time.
So I tried the following two methods to simply let my mainloops update function get called from the NSApplications mainloop:

  1. Post a custom Event to NSApplication, overwrite NSApplications
    sendEvent: function and simply call my mainloops update function
    from there. Similar to this:

    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
        }
    }
    

    This was only a good idea in theory because if my app updated very
    quickly (for instance due to a timer firing quickly), the whole
    cocoa event queue became totoally unresponsive because I added so
    many custom events. So don't use this...

  2. Use performSelectorOnMainThread with a cocoaFunction that in
    turn calls my update function

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

    This was alot better, the app and cocoa EventLoop was very
    responsive. If you are only trying to achieve something simple I'd
    recommend going down this route since it is the easiest of the ones
    proposed here. Anyways I had very little control about the order of
    things happening with this approach (this is crucial if you have a
    multithreaded app), i.e when my timers fired and would do a rather
    long job, often times they would reschedule before any new
    mouse/keyboard input could be added to my eventQueue and thus would
    make the whole input sluggish. Turnin on Vertical Sync on a window
    that was drawn by a repeating timer was enough to let this happen.

  3. After all I had to come back to nextEventMatchingMask:untilDate:inMode:dequeue: and after some trial and error I actually found a way to make it work without constant polling. The structure of my loop is similar to this:

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

    where pollEvents and idle are the important functions, basically I use something similar to this.

    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);
    }
    

    To implement the blocking inside the idle() function I did this (not sure if this is good, but it seems to work great!) :

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

    this causes cocoa to wait till there is an event, if that happenes idle simply exits and the loopfunc starts again. To wake up the idle function if i.e. one of my timers (i dont use cocoa timers) fires, i once again use a custom event:

    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];
    }
    

    Since I clear the whole cocoa event queue right afterwards i don't have the same problems as described in section 1.
    However, there are some drawbacks with this approach aswell because I think it does not do everything that [NSApplication run] is doing internally, i.e. application delegate things like this:

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

    don't seem to work, anyways I can live with that since you can easily check yourself if the last window just closed.

I know this answer is pretty lengthy, but so was my journey to get there. I hope this helps someone and prevents people from doing the mistakes I did.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文