34

私はOSXでCocoaを既存のc/c ++メインループと一緒に使用しようとした最初の人ではないことを知っていますが、これまでに出会ったソリューションはあまり好きではないので、別のアイデアを思いつきました。 d話し合いたい。私が見つけた最も一般的な方法(glut、glfw、SDL、そしてQTでも)は、ポーリングを使用してNSApplications runメソッドを置き換え、イベントを自分で処理することです。

nextEventMatchingMask:untilDate:inMode:dequeue:

これには、新しいイベントがあるかどうかを確認するために常にポーリングする必要があるため、CPUが実際にアイドル状態になることはないという大きな欠点があります。さらに、NSApplications run関数内で行われているのはそれだけではないため、この代替品を使用してください。

だから私がやりたいのは、カカオのrunLoopを無傷に保つことです。独自のタイマーメソッドがc++で実装されていると想像してください。これらのメソッドは通常、メインループ内で管理および起動されます(これは例としてのほんの一部です)。私の考えは、すべてのループ部分をセカンダリスレッドに移動し(NSApplicationの実行は、私の知る限りメインスレッドから呼び出す必要があるため)、カスタムイベントを派生バージョンのNSApplicationに投稿して、その中で適切に処理することです。 sendEvent:メソッド。たとえば、タイマーがc ++ループの発火で測定された場合、NSApplicationにカスタムイベントを送信します。NSApplicationは、アプリケーションのloopFunc()関数(メインスレッドにも存在します)を実行し、c++イベントチェーンにイベントを適切に送信します。 。それで、まず第一に、これは良い解決策になると思いますか?そうであれば、

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

次に、次のようなものを使用します。

[NSApp postEvent:atStart:]

NSApplicationに通知します。

(otherEventWithTypeで)ウィンドウに関する情報なしでイベントを投稿したいのですが、その部分を単に無視できますか?

次に、次のようなNSApplicationssendEvent関数を上書きすることを想像します。

    - (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内のカスタムイベントを投稿して確認する方法ですか。これは、ポーリングせずにココアを既存の実行ループに統合するための有効なアプローチだと思いますか?

4

1 に答える 1

30

さて、これは私が予想していたよりもずっと時間がかかったので、私が試したことの概要を説明し、私がそれらでどのような経験をしたかをお話ししたいと思います。これにより、Cocoaを既存のメインループに統合しようとする人々が将来多くの時間を節約できることを願っています。議論された事柄を検索したときに私が最初に見つけた関数は関数でした

nextEventMatchingMask:untilDate:inMode:dequeue:

しかし、質問で述べたように、これに関する私の主な問題は、CPU時間をかなり浪費する新しいイベントを常にポーリングする必要があることでした。そこで、次の2つの方法を試して、メインループの更新関数をNSApplicationsメインループから呼び出せるようにしました。

  1. カスタムイベントをNSApplicationに投稿し、NSApplications関数を上書き しsendEvent:て、そこからmainloopsupdate関数を呼び出すだけです。これに似ています:

    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. cocoaFunctionでperformSelectorOnMainThreadを使用すると、更新関数が呼び出されます。

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

    これはかなり良く、アプリとココアの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;
    }
    

    これにより、ココアはイベントが発生するまで待機します。イベントが発生した場合、アイドル状態が終了し、loopfuncが再開されます。つまり、タイマーの1つ(ココアタイマーは使用しません)が起動した場合にアイドル機能をウェイクアップするには、もう一度カスタムイベントを使用します。

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

    最後のウィンドウが閉じたかどうかを簡単に確認できるので、とにかく私はそれと一緒に暮らすことができます。

この答えはかなり長いことは知っていますが、そこにたどり着くまでの道のりもそうだったのです。これが誰かを助け、人々が私がした間違いをするのを防ぐことを願っています。

于 2011-08-12T12:01:52.067 に答える