Cocos2d-x 學習筆記(15.2) EventDispatcher 事件分發機制 dispatchEvent(event)


1. 事件分發方法 EventDispatcher::dispatchEvent(Event* event)

首先通過_isEnabled標志判斷事件分發是否啟用。

執行 updateDirtyFlagForSceneGraph()。把一些node對應的ID置臟標記。

對_inDispatch++,當前正在分發的事件數+1。

    DispatchGuard guard(_inDispatch);

接下來是一個判斷,如果是觸摸事件,會調用觸摸專用的分發方法,而不是本方法。

    if (event->getType() == Event::Type::TOUCH)
    {
        dispatchTouchEvent(static_cast<EventTouch*>(event));
        return;
    }

獲取參數事件的ID作為監聽器ID。

    auto listenerID = __getListenerID(event);

接下來對事件同ID的所有監聽器進行排序。

    sortEventListeners(listenerID);

又是一個類型判斷,如果是鼠標事件,定義觸摸事件分發函數指針,否則,定義通用的事件分發函數指針。

    auto pfnDispatchEventToListeners = &EventDispatcher::dispatchEventToListeners;
    if (event->getType() == Event::Type::MOUSE) {
        pfnDispatchEventToListeners = &EventDispatcher::dispatchTouchEventToListeners;
    }

然后通過參數事件監聽器ID從_listenerMap中找到對應的Vector,該類包含兩個存儲監聽器的容器。

    auto iter = _listenerMap.find(listenerID);
    if (iter != _listenerMap.end())
    {
        auto listeners = iter->second;
        //...
    }

定義匿名函數。

        auto onEvent = [&event](EventListener* listener) -> bool{
            event->setCurrentTarget(listener->getAssociatedNode());
            listener->_onEvent(event);
            return event->isStopped();
        };

進行事件分發。

(this->*pfnDispatchEventToListeners)(listeners, onEvent);

最后對所有待添加和待刪除的監聽器進行處理。

updateListeners(event);

簡而言之,事件分發的邏輯是,通過參數事件,找到事件對應的監聽器ID,分發前還要判斷ID對應的監聽器容器是否需要重新排序,把該事件分發給所有同ID監聽器的回調函數進行處理。

接下來對一些重點方法進行學習。

2. updateDirtyFlagForSceneGraph()

當調用resumeEventListenersForTarget方法,把node的所有關聯監聽器從暫停狀態恢復時,需要把node加入_dirtyNodes。

該函數是就是把_dirtyNodes中的node相關的曾經暫停的監聽器的ID在_priorityDirtyFlagMap置臟標記SCENE_GRAPH_PRIORITY,對這些ID的監聽器容器之后重新排序。

3. DispatchGuard guard(_inDispatch)

 創建了DispatchGuard類的對象,_inDispatch作為構造函數。

DispatchGuard(int& count):
            _count(count)
    {
        ++_count;
    }

    ~DispatchGuard()
    {
        --_count;
    }

可以看出,對一件事件進行分發時,_inDispatch++。在分發方法結束時,會對這個局部對象析構,_inDispatch--。十分巧妙的實現了對_inDispatch的自動管理。

4. sortEventListeners(listenerID)

簡要的說,在_priorityDirtyFlagMap中判斷每種ID的臟標記,根據臟標記的不同,決定ID的哪些容器要重新排序。

該方法首先獲取待排序的監聽器ID的臟標記。

    DirtyFlag dirtyFlag = DirtyFlag::NONE;
    
    auto dirtyIter = _priorityDirtyFlagMap.find(listenerID);
    if (dirtyIter != _priorityDirtyFlagMap.end())
    {
        dirtyFlag = dirtyIter->second;
    }

臟標記不為NONE,說明容器需要重新排序,於是先把臟標記置NONE,接下來開始排序。臟標記為NONE時,因為已排好序,排序函數執行完成。

 if (dirtyFlag != DirtyFlag::NONE)
    {
        dirtyIter->second = DirtyFlag::NONE;
//...

這里用按位與操作判斷是否對ID的兩個容器排序。根據按位與的結果,可能兩容器都要重新排序,也可能只有一個容器需要排序。

        if ((int)dirtyFlag & (int)DirtyFlag::FIXED_PRIORITY)
        {
            sortEventListenersOfFixedPriority(listenerID);
        }
        
        if ((int)dirtyFlag & (int)DirtyFlag::SCENE_GRAPH_PRIORITY)
        {
            auto rootNode = Director::getInstance()->getRunningScene();
            if (rootNode)
            {
                sortEventListenersOfSceneGraphPriority(listenerID, rootNode);
            }
            else
            {
                dirtyIter->second = DirtyFlag::SCENE_GRAPH_PRIORITY;
            }
        }

對兩個容器排序分別用到了兩個方法:

            sortEventListenersOfFixedPriority(listenerID);
            sortEventListenersOfSceneGraphPriority(listenerID, rootNode);

4.1 sortEventListenersOfFixedPriority(listenerID)

該方法首先獲取ID對應的fixedListeners容器。

    auto listeners = getListeners(listenerID);

    if (listeners == nullptr)
        return;
    
    auto fixedListeners = listeners->getFixedPriorityListeners();
    if (fixedListeners == nullptr)
        return;

對容器進行排序,按優先級從小到大的順序。

std::stable_sort(fixedListeners->begin(), fixedListeners->end(), [](const EventListener* l1, const EventListener* l2) {
        return l1->getFixedPriority() < l2->getFixedPriority();
    });

對排好序的容器從小到大查找,找到第一個優先級不小於0的監聽器,把其下標記錄,作為Vector的成員_gt0Index。

    int index = 0;
    for (auto& listener : *fixedListeners)
    {
        if (listener->getFixedPriority() >= 0)
            break;
        ++index;
    }
    
    listeners->setGt0Index(index);

4.2 sortEventListenersOfSceneGraphPriority(listenerID, rootNode)

參數rootNode是當前運行的場景。

同上面的排序一樣,顯先獲取容器。不同之處在於sceneGraphListeners容器里的監聽器優先級都為0,排序需要按照node的順序。

需要_nodePriorityIndex容器記錄node的優先級。

    _nodePriorityIndex = 0;
    _nodePriorityMap.clear();

    visitTarget(rootNode, true);

visitTarget方法將計算好的node和node優先級存儲在_nodePriorityMap。接下來對sceneGraphListeners進行排序,排序依照每個監聽器關聯的node在_nodePriorityMap的優先級大小,node優先級大,監聽器排序在前。

std::stable_sort(sceneGraphListeners->begin(), sceneGraphListeners->end(), [this](const EventListener* l1, const EventListener* l2) {
        return _nodePriorityMap[l1->getAssociatedNode()] > _nodePriorityMap[l2->getAssociatedNode()];
    });

4.3 visitTarget(rootNode, true)

簡要的說,將計算好的node和node優先級存儲在_nodePriorityMap

該方法首先對node的子節點排序。排序后子節點按LocalZOrder從小到大排列,相同時按添加到node的順序(即順序不變)。

node->sortAllChildren();

獲取子節點容器,數量。

    auto& children = node->getChildren();
    auto childrenCount = children.size();

對children進行中序遍歷,遍歷到的node的globalZOrder和node存入_globalZOrderNodeMap中。此時,map中的每個node容器中node都是按LocalZOrder從小到大排列。

 if(childrenCount > 0)
    {
        Node* child = nullptr;
        // visit children zOrder < 0
        for( ; i < childrenCount; i++ )
        {
            child = children.at(i);
            
            if ( child && child->getLocalZOrder() < 0 )
                visitTarget(child, false);
            else
                break;
        }
        
        if (_nodeListenersMap.find(node) != _nodeListenersMap.end())
        {
            _globalZOrderNodeMap[node->getGlobalZOrder()].push_back(node);
        }
        
        for( ; i < childrenCount; i++ )
        {
            child = children.at(i);
            if (child)
                visitTarget(child, false);
        }
    }
    else
    {
        if (_nodeListenersMap.find(node) != _nodeListenersMap.end())
        {
            _globalZOrderNodeMap[node->getGlobalZOrder()].push_back(node);
        }
    }

場景節點中,先獲取場景中所有節點globalZOrder,並對globalZOrder從小到大排序。

遍歷_globalZOrderNodeMap,獲取每個node。遍歷按globalZOrder從小到大的順序,相同globalZOrder則按先后順序(LocalZOrder從小到大)遍歷。按遍歷的順序,將node依次添加到_nodePriorityMap。優先級按node的順序依次+1。即,越晚繪制的node優先級越高。

   if (isRootNode)
    {
        std::vector<float> globalZOrders; //存儲scene中所有node的globalZOrder
        globalZOrders.reserve(_globalZOrderNodeMap.size());
        
        for (const auto& e : _globalZOrderNodeMap)
        {
            globalZOrders.push_back(e.first);
        }
        
        std::stable_sort(globalZOrders.begin(), globalZOrders.end(), [](const float a, const float b){
            return a < b;
        }); //globalZOrder從小到大排序
        
        for (const auto& globalZ : globalZOrders)
        {
            for (const auto& n : _globalZOrderNodeMap[globalZ])
            {
                _nodePriorityMap[n] = ++_nodePriorityIndex;
            }
        }
        
        _globalZOrderNodeMap.clear();
    }

5. 進行事件分發 dispatchEventToListeners(listeners, onEvent)

函數指針pfnDispatchEventToListeners根據事件ID是否是鼠標類型指向不同的函數。

以非觸摸dispatchEventToListeners為例。

首先獲取ID的兩個監聽器容器:fixedPriorityListeners sceneGraphPriorityListeners。

按照優先級<0 =0 >0的順序,對每個監聽器執行以下代碼。fixedPriorityListeners通過getGt0Index()獲取優先級大於0的監聽器序號為分界點,進行分類。

                if (l->isEnabled() && !l->isPaused() && l->isRegistered() && onEvent(l))
                {
                    shouldStopPropagation = true;
                    break;
                }

這里調用了之前定義的匿名函數onEvent(listener)。

這里有單獨介紹

6. 匿名函數 onEvent(listener)

設置事件的_currentTarget為監聽器關聯的node,監聽器執行回調函數_onEvent(event)對事件進行處理。

        auto onEvent = [&event](EventListener* listener) -> bool{
            event->setCurrentTarget(listener->getAssociatedNode());
            listener->_onEvent(event);
            return event->isStopped();
        };

7. 收尾處理 updateListeners(event)

刪除所有待刪除容器里的監聽器。添加所有待添加容器里的監聽器。刪除Vector里isRegistered為false的監聽器。刪除_listenerMap中Vector為空的元素。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM