草庐IT

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

coder 2023-05-02 原文

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

nextEventMatchingMask:untilDate:inMode:dequeue:

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

所以我想做的是保持 cocoa runLoop 完好无损。想象一下,您将在 c++ 中实现自己的计时器方法,这些方法通常会在您的主循环中进行管理和触发(这只是一个示例)。我的想法是将所有循环部分移动到辅助线程(因为据我所知,需要从主线程调用 NSApplication 运行),然后将自定义事件发布到我的 NSApplication 派生版本,该版本在其内部适本地处理它们发送事件:方法。例如,如果我的计时器在我的 c++ 循环中触发,我会向 NSApplication 发布一个自定义事件,该事件反过来运行我的应用程序的 loopFunc() 函数(也位于主线程中),该函数会适本地将事件发送到我的 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 集成到现有运行循环中的有效方法吗?

最佳答案

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

nextEventMatchingMask:untilDate:inMode:dequeue:

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

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

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

  2. 将 performSelectorOnMainThread 与 cocoaFunction 一起使用 轮流调用我的更新函数

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

    这好多了,app 和 cocoa EventLoop 非常好 react 灵敏。如果您只是想实现一些简单的事情,我会 建议沿着这条路线走,因为它是最简单的路线 在这里提出。无论如何,我几乎无法控制顺序 这种方法发生的事情(如果你有一个 多线程应用程序),即当我的计时器被触发并且会做一个相当 长期工作,通常他们会在任何新的工作之前重新安排 鼠标/键盘输入可以添加到我的 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 再次启动。如果我的一个计时器(我不使用 cocoa 计时器)触发,要唤醒空闲功能,我再次使用自定义事件:

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

    由于我随后立即清除了整个 cocoa 事件队列,因此我没有遇到 第 1 节中描述的相同问题。 但是,这种方法也有一些缺点,因为我认为它并没有完成 [NSApplication run] 在内部所做的所有事情,即应用程序委托(delegate)这样的事情:

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

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

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

关于c++ - Cocoa:将 NSApplication 集成到现有的 c++ 主循环中,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6732400/

有关c++ - Cocoa:将 NSApplication 集成到现有的 c++ 主循环中的更多相关文章

  1. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  2. ruby-on-rails - 如何使辅助方法在 Rails 集成测试中可用? - 2

    我在app/helpers/sessions_helper.rb中有一个帮助程序文件,其中包含一个方法my_preference,它返回当前登录用户的首选项。我想在集成测试中访问该方法。例如,这样我就可以在测试中使用getuser_path(my_preference)。在其他帖子中,我读到这可以通过在测试文件中包含requiresessions_helper来实现,但我仍然收到错误NameError:undefinedlocalvariableormethod'my_preference'.我做错了什么?require'test_helper'require'sessions_hel

  3. ruby-on-rails - 我如何将 Hoptoad 与 DelayedJob 和 DaemonSpawn 集成? - 2

    我一直很高兴地使用DelayedJob习惯用法:foo.send_later(:bar)这会调用DelayedJob进程中对象foo的方法bar。我一直在使用DaemonSpawn在我的服务器上启动DelayedJob进程。但是...如果foo抛出异常,Hoptoad不会捕获它。这是任何这些包中的错误...还是我需要更改某些配置...或者我是否需要在DS或DJ中插入一些异常处理来调用Hoptoad通知程序?回应下面的第一条评论。classDelayedJobWorker 最佳答案 尝试monkeypatchingDelayed::W

  4. ruby - 使用 `+=` 和 `send` 方法 - 2

    如何将send与+=一起使用?a=20;a.send"+=",10undefinedmethod`+='for20:Fixnuma=20;a+=10=>30 最佳答案 恐怕你不能。+=不是方法,而是语法糖。参见http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_expressions.html它说Incommonwithmanyotherlanguages,Rubyhasasyntacticshortcut:a=a+2maybewrittenasa+=2.你能做的最好的事情是:

  5. jenkins部署1--jenkins+gitee持续集成 - 2

    前置步骤我们都操作完了,这篇开始介绍jenkins的集成。话不多说,看操作1、登录进入jenkins后会让你选择安装插件,选择第一个默认的就行。安装完成后设置账号密码,重新登录。2、配置JDK和Git都需要执行路径,所以需要先把执行路径找到,先进入服务器的docker容器,2.1JDK的路径root@69eef9ee86cf:/usr/bin#echo$JAVA_HOME/usr/local/openjdk-82.2Git的路径root@69eef9ee86cf:/#whichgit/usr/bin/git3、先配置JDK和Git。点击:ManageJenkins>>GlobalToolCon

  6. objective-c - 在设置 Cocoa Pods 和安装 Ruby 更新时出错 - 2

    我正在尝试为我的iOS应用程序设置cocoapods但是当我执行命令时:sudogemupdate--system我收到错误消息:当前已安装最新版本。中止。当我进入cocoapods的下一步时:sudogeminstallcocoapods我在MacOS10.8.5上遇到错误:ERROR:Errorinstallingcocoapods:cocoapods-trunkrequiresRubyversion>=2.0.0.我在MacOS10.9.4上尝试了同样的操作,但出现错误:ERROR:Couldnotfindavalidgem'cocoapods'(>=0),hereiswhy:U

  7. ruby - 如何计算 Liquid 中的变量 +1 - 2

    我对如何计算通过{%assignvar=0%}赋值的变量加一完全感到困惑。这应该是最简单的任务。到目前为止,这是我尝试过的:{%assignamount=0%}{%forvariantinproduct.variants%}{%assignamount=amount+1%}{%endfor%}Amount:{{amount}}结果总是0。也许我忽略了一些明显的东西。也许有更好的方法。我想要存档的只是获取运行的迭代次数。 最佳答案 因为{{incrementamount}}将输出您的变量值并且不会影响{%assign%}定义的变量,我

  8. arrays - Ruby 数组 += vs 推送 - 2

    我有一个数组数组,想将元素附加到子数组。+=做我想做的,但我想了解为什么push不做。我期望的行为(并与+=一起工作):b=Array.new(3,[])b[0]+=["apple"]b[1]+=["orange"]b[2]+=["frog"]b=>[["苹果"],["橙子"],["Frog"]]通过推送,我将推送的元素附加到每个子数组(为什么?):a=Array.new(3,[])a[0].push("apple")a[1].push("orange")a[2].push("frog")a=>[[“苹果”、“橙子”、“Frog”]、[“苹果”、“橙子”、“Frog”]、[“苹果”、“

  9. ruby-on-rails - 在现有数据库上进行 Rails 迁移 - 2

    我正在创建一个新的Rails3.1应用程序。我希望这个新应用程序重用现有数据库(由以前的Rails2应用程序创建)。我创建了新的应用程序定义模型,它重用了数据库中的一些现有数据。在开发和测试阶段,一切正常,因为它在干净的表数据库上运行,但是当尝试部署到生产环境时,我收到如下消息:PGError:ERROR:column"email"ofrelation"users"alreadyexists***[err::localhost]:ALTERTABLE"users"ADDCOLUMN"email"charactervarying(255)DEFAULT''NOTNULL但是我在迁移中有这

  10. ruby - 如何将新的 rvm 安装与现有的 ruby​​ 版本相关联? - 2

    我遇到了RVM的问题,所以我卸载并重新安装了它。事实是我实际上尝试过rbenv,但这对我来说没有用,所以我试图让rvm重新启动并运行-而不必安装重复版本的Ruby。我至少安装了1个现有版本的Ruby:ruby--versionruby1.8.7(2011-12-28patchlevel357)[universal-darwin11.0]但是当我执行rvmlist时,我得到一个空白列表:bash-3.2$rvmlistrvmrubies#Defaultrubynotset.Try'rvmaliascreatedefault'.#=>-current#=*-current&&default

随机推荐