Cocos2d-X3.0 刨根問底(七)----- 事件機制Event源碼分析


這一章,我們來分析Cocos2d-x 事件機制相關的源碼, 根據Cocos2d-x的工程目錄,我們可以找到所有關於事件的源碼都存在放在下圖所示的目錄中。

4

從這個event_dispatcher目錄中的文件命名上分析 cocos2d-x與事件相關的類一共有四種, Event, EventListener,EventDispatcher, Touch分別為 事件,事件偵聽器,事件分發器,觸摸

我們先從Event類開始。

打開CCEvent.h文件

/**
 *   Base class of all kinds of events.
 */
class Event : public Ref
{
public:
    enum class Type
    {
        TOUCH,
        KEYBOARD,
        ACCELERATION,
        MOUSE,
        CUSTOM
    };
    
protected:
    /** Constructor */
    Event(Type type);
public:
    /** Destructor */
    virtual ~Event();

    /** Gets the event type */
    inline Type getType() const { return _type; };
    
    /** Stops propagation for current event */
    inline void stopPropagation() { _isStopped = true; };
    
    /** Checks whether the event has been stopped */
    inline bool isStopped() const { return _isStopped; };
    
    /** @brief Gets current target of the event
     *  @return The target with which the event associates.
     *  @note It onlys be available when the event listener is associated with node. 
     *        It returns 0 when the listener is associated with fixed priority.
     */
    inline Node* getCurrentTarget() { return _currentTarget; };
    
protected:
    /** Sets current target */
    inline void setCurrentTarget(Node* target) { _currentTarget = target; };
    
    Type _type;     ///< Event type
    
    bool _isStopped;       ///< whether the event has been stopped.
    Node* _currentTarget;  ///< Current target
    
    friend class EventDispatcher;
};

這個類並且不復雜,先看一下類的注釋,Event類是所有事件類的基類。

類定義的最上面有一個枚舉,定義了事件的類型

enum class Type
    {
        TOUCH,
        KEYBOARD,
        ACCELERATION,
        MOUSE,
        CUSTOM
    };

事件的各類分別為 ,觸摸事件, 鍵盤事件, 加速器事件,鼠標事件, 用戶自定義事件。

再看一下Event類的成員變量

_type 描述當前對象的事件類型。

_isStopped 描述當前事件是否已經停止

_currentTarget 是偵聽事件的Node類型的對象

就這三個成員變量,含義很簡單,Event類的幾個方法也沒什么特別的,就是成員變量的get set方法。

下面我們再看一下用戶自定義事件 EventCustom  類的定義,來了解一下。

class EventCustom : public Event
{
public:
    /** Constructor */
    EventCustom(const std::string& eventName);
    
    /** Sets user data */
    inline void setUserData(void* data) { _userData = data; };
    
    /** Gets user data */
    inline void* getUserData() const { return _userData; };
    
    /** Gets event name */
    inline const std::string& getEventName() const { return _eventName; };
protected:
    void* _userData;       ///< User data
    std::string _eventName;
};

 

在自定義事件類中,多出了兩個成員變量 一個是 _userData用來記錄用戶自定義數據,另一個_eventName用戶給這個事件起的別名。

其它的關於Event的子類,大家可以自己看一下,內容都差不多。

 

下面我們來分析 EventListener 這個類。

這個類看着挺長,其實沒什么內容,先看下類的定義。

EventListener也同樣定義了一個類型

enum class Type
    {
        UNKNOWN,
        TOUCH_ONE_BY_ONE,
        TOUCH_ALL_AT_ONCE,
        KEYBOARD,
        MOUSE,
        ACCELERATION,
        CUSTOM
    };

這個類型與Event的類型有一小點不同,就是將觸摸事件類型分成了 One by One (一個接一個) 與  All At Once (同時一起)兩種。

再看 EventListener 的屬性

std::function<void(Event*)> _onEvent;   // 用來記錄偵聽器回調函數

    Type _type;                             // 偵聽器的類型
    ListenerID _listenerID;                 // 偵聽器的ID 其實是個字符串
    bool _isRegistered;                     // 標記當前偵聽器是否已經加入到了事件分發器中的狀態變量

    int   _fixedPriority;   // 偵聽器的優先級別,數值越高級別越高.默認為0
    Node* _node;            // 場景結點(這里這個變量的作用還沒能理解好,后面我們再進行分析)
    bool _paused;           // 標記此偵聽器是否為暫停狀態。
    bool _isEnabled;        // 標記此偵聽器是否有效
上面分析了屬性的功能 。EventListener的很簡單,大部分都是屬性的讀寫方法(get/set)這里就不多說了,下面我們重點看一下init方法及實現。
bool EventListener::init(Type t, const ListenerID& listenerID, const std::function<void(Event*)>& callback)
{
    _onEvent = callback;
    _type = t;
    _listenerID = listenerID;
    _isRegistered = false;
    _paused = true;
    _isEnabled = true;
    
    return true;
}

這個init函數 也很簡單,就是一些成員變量的賦值操作。

在EventListener定義中有兩個純虛函數,我們看一下。

/** Checks whether the listener is available. */
    virtual bool checkAvailable() = 0;

    /** Clones the listener, its subclasses have to override this method. */
    virtual EventListener* clone() = 0;

通過注釋了解這兩個函數 一個是驗證listener是否有效 別一個是clone方法。

EventListener是抽象類,那么咱們找一個它的子類的具體實現。

我們看一下EventListenerCustom這個類的定義。

class EventListenerCustom : public EventListener
{
public:
    /** Creates an event listener with type and callback.
     *  @param eventType The type of the event.
     *  @param callback The callback function when the specified event was emitted.
     */
    static EventListenerCustom* create(const std::string& eventName, const std::function<void(EventCustom*)>& callback);
    
    /// Overrides
    virtual bool checkAvailable() override;
    virtual EventListenerCustom* clone() override;
    
CC_CONSTRUCTOR_ACCESS:
    /** Constructor */
    EventListenerCustom();
    
    /** Initializes event with type and callback function */
    bool init(const ListenerID& listenerId, const std::function<void(EventCustom*)>& callback);
    
protected:
    std::function<void(EventCustom*)> _onCustomEvent;
    
    friend class LuaEventListenerCustom;
};

EventListenerCustom又增加了一個成員變量,_onCustomEvent 接收一個EventCustom類型的事件為參數的回調函數。

我們可以看到,這個類的構造函數不是public形式的,所以這個類不能直接被實例化,

找到了EventListenerCustom提供了一個靜態函數 create 所以實例化這個在的對象一定要使用這個create方法。

我們看一下create方法。

EventListenerCustom* EventListenerCustom::create(const std::string& eventName, const std::function<void(EventCustom*)>& callback)
{
    EventListenerCustom* ret = new EventListenerCustom();
    if (ret && ret->init(eventName, callback))
    {
        ret->autorelease();
    }
    else
    {
        CC_SAFE_DELETE(ret);
    }
    return ret;
}

這個Create函數的結構,與Node的Create結構一樣,新創建的EventListener加入到了autorelease列表里面,在Create的時候調用了init函數,我們再看一下EventListenerCustom::init方法。

bool EventListenerCustom::init(const ListenerID& listenerId, const std::function<void(EventCustom*)>& callback)
{
    bool ret = false;
    
    _onCustomEvent = callback;
    
    auto listener = [this](Event* event){
        if (_onCustomEvent != nullptr)
        {
            _onCustomEvent(static_cast<EventCustom*>(event));
        }
    };
    
    if (EventListener::init(EventListener::Type::CUSTOM, listenerId, listener))
    {
        ret = true;
    }
    return ret;
}

這個函數也沒什么特別的,但值得注意的是,CustomEvent的回調與基類的回調函數是怎么關聯的。

在EventListenerCustom類中有一個成員變量_onCustomEvent它是一個函數指針來記錄事件觸發后的回調函數。

在EventListener類中也有一個_onEvent成員變量來記錄事件觸發的時候的回調函數。

EventListenerCustom::init函數中 有一個匿名函數,listener 在這個匿名函數中 判斷了_onCustomEvent是否為空,如果不為空那么調用_onCustomEvent。

后面調用基類的init方法,把這個匿名函數傳遞給了基類的_onEvent,這樣基類的回調函數_onEvent與子類的_onCustomEvent就關聯起來了。

下面我們看一下抽象方法在子類中怎么實現的

EventListenerCustom* EventListenerCustom::clone()
{
    EventListenerCustom* ret = new EventListenerCustom();
    if (ret && ret->init(_listenerID, _onCustomEvent))
    {
        ret->autorelease();
    }
    else
    {
        CC_SAFE_DELETE(ret);
    }
    return ret;
}

clone的操作與Create方法很象,其實就是重新創建了一個對象,它的_listenerID 、 _onCustomEvent值與原對象一樣。返回的是新創建的對象指針。

事件類Event有了,偵聽器類EventListener 有了,那么下面我們來分析事件的分發器EventDispatcher。

打開CCEventDispatcher.h文件

/**
This class manages event listener subscriptions
and event dispatching.

The EventListener list is managed in such a way that
event listeners can be added and removed even
from within an EventListener, while events are being
dispatched.
*/
class EventDispatcher : public Ref
{

這個類也是Ref的子類,從注釋上面我們先整體的了解EventDispatcher的功能及作用。這個類管理事件偵聽腳本及事件的分發處理。有一個事件偵聽器列表,來記錄所有偵聽的事件。分析這個類我們首先還是從這個類的屬性上開始。

    /** 保存所有事件偵聽器的列表*/
    std::unordered_map<EventListener::ListenerID, EventListenerVector*> _listenerMap;
    
    /** 有臟標記的偵聽器列表,具體什么是臟標記后面再分析。 */
    std::unordered_map<EventListener::ListenerID, DirtyFlag> _priorityDirtyFlagMap;
    
    /** Node類型結點的事件偵聽器列表 */
    std::unordered_map<Node*, std::vector<EventListener*>*> _nodeListenersMap;
    
    /** Node結點與事件級別的列表, 看到這里還不太明白這個列表是什么作用,后面着重尋找答案*/
    std::unordered_map<Node*, int> _nodePriorityMap;
    
    /** 記錄結點的ZOrder與結點的指針列表 也不太知道這是干什么用的,后面找答案*/
    std::unordered_map<float, std::vector<Node*>> _globalZOrderNodeMap;
    
    /** 事件分必后加入的偵聽器列表*/
    std::vector<EventListener*> _toAddedListeners;
    
    /** 與場景優先級偵聽器相關聯的node結點。*/
    std::set<Node*> _dirtyNodes;
    
    /** 描述事件分發器是否在分發事件 */
    int _inDispatch;
    
    /** 標記是否開啟事件分發*/
    bool _isEnabled;
    /** 優先級索引*/ 
    int _nodePriorityIndex;
    
    /** 內部自定義偵聽器索引*/ 
    std::set<std::string> _internalCustomListenerIDs;

上面針對這個類的屬性做了字面上的分析,大部分屬性目前還不知道具體功能,不過沒關系,后面我們一個一個去破解它的含義。

現在 我們來分析一下這個類的構造函數

EventDispatcher::EventDispatcher()
: _inDispatch(0)
, _isEnabled(false)
, _nodePriorityIndex(0)
{
    _toAddedListeners.reserve(50);
    
    // fixed #4129: Mark the following listener IDs for internal use.
    // Therefore, internal listeners would not be cleaned when removeAllEventListeners is invoked.
    _internalCustomListenerIDs.insert(EVENT_COME_TO_FOREGROUND);
    _internalCustomListenerIDs.insert(EVENT_COME_TO_BACKGROUND);
}

構造函數不復雜,除了初始幾個變量,我們可以看到,向_internalCustomListenerIDs加入了兩個自定義的事件,這里還有一行注釋,說明,在清除所有事件偵聽器的時候內容偵聽器是不會被清除的。

我們看一下這兩個內容自定義的事件,一個是程序返回到后台,一個是程序返回到前台。估計這在這兩個事件里面要做一些暫停的工作。

從EventDispatcher的成員變量上看,都是圍繞着偵聽器列表來定義的,那么我們就看一下把偵聽器加入到偵聽器列表的方法。

第一個add方法

/** Adds a event listener for a specified event with the priority of scene graph.
     *  @param listener The listener of a specified event.
     *  @param node The priority of the listener is based on the draw order of this node.
     *  @note  The priority of scene graph will be fixed value 0. So the order of listener item
     *          in the vector will be ' <0, scene graph (0 priority), >0'.
     */
    void addEventListenerWithSceneGraphPriority(EventListener* listener, Node* node);

從注釋上可以知道這個方法的作用是,將一個指定的事件偵聽器依照場景圖的優先級順序加入到偵聽器列表里面, 這個方法與場景圖的繪制順序有關系,

場景的結點渲染順序也就是zOrder的順序,場景中的結點優先級一般都是0,偵聽器的存放順序就是 小於0  等於0 大於0這樣一個順序 。

我們看一下這個方法的實現

void EventDispatcher::addEventListenerWithSceneGraphPriority(EventListener* listener, Node* node)
{
    CCASSERT(listener && node, "Invalid parameters.");
    CCASSERT(!listener->isRegistered(), "The listener has been registered.");
    
    if (!listener->checkAvailable())
        return;
    
    listener->setAssociatedNode(node);
    listener->setFixedPriority(0);
    listener->setRegistered(true);
    
    addEventListener(listener);
}

這個方法內容也簡單,

1. 先檢查了偵聽器是否有效

2. 將結點與偵聽器做了關聯

3. 設置優先級為0,從注釋上我們已經得到這個信息了,這個方法加入的偵聽器都是顯示對象的,所以優先級都為0

4. 設置偵聽器已經注冊狀態

5. 調用了addEventListener方法,將偵聽器加入到EventDispatcher的偵聽器列表里。

下面我們看一下addEventListener方法,了解是將偵聽器加入到偵聽器管理列表里的過程

void EventDispatcher::addEventListener(EventListener* listener)
{
    if (_inDispatch == 0)
    {
        forceAddEventListener(listener);
    }
    else
    {
        _toAddedListeners.push_back(listener);
    }

    listener->retain();
}

這個方法判斷了當前 是否在分發消息,如果沒有分發消息那么就調用  forceAddEventListener 把偵聽器加入到偵聽器列表里面。

如果_indispatch不為0證明現在正在分發消息那么新加入的偵聽器就放到了臨時數組_toAddedListeners里面

不管管理器是不是在分發消息listener都有一個歸宿,那么最后增加了listener一次引用計數。

下面我們看一下forceAddEventListener方法。

void EventDispatcher::forceAddEventListener(EventListener* listener)
{
    EventListenerVector* listeners = nullptr;
    EventListener::ListenerID listenerID = listener->getListenerID();
    auto itr = _listenerMap.find(listenerID);
    if (itr == _listenerMap.end())
    {
        
        listeners = new EventListenerVector();
        _listenerMap.insert(std::make_pair(listenerID, listeners));
    }
    else
    {
        listeners = itr->second;
    }
    
    listeners->push_back(listener);
    
    if (listener->getFixedPriority() == 0)
    {
        setDirty(listenerID, DirtyFlag::SCENE_GRAPH_PRIORITY);
        
        auto node = listener->getAssociatedNode();
        CCASSERT(node != nullptr, "Invalid scene graph priority!");
        
        associateNodeAndEventListener(node, listener);
        
        if (node->isRunning())
        {
            resumeEventListenersForTarget(node);
        }
    }
    else
    {
        setDirty(listenerID, DirtyFlag::FIXED_PRIORITY);
    }
}

這個類里面涉及到了一個EventDispatcher的內部類 EventListenerVector 這樣一個數據結構,

這個結構在這里不多分析了,很簡單,這個結構里封裝了兩個數組,_fixedListeners 與_sceneGraphListeners ,分別保存優先級不為0的偵聽器指針與優先級為0的偵聽器指針。

我們看一下強制將一個偵聽器加入到管理列表的過程

  1. _listenerMap是按照偵聽器ID來做分類的,每個偵聽器ID都有一個EventListenerVector 數組。在_listenerMap中找 listenerID與要加入的listener相同的偵聽器列表
  2. 如果沒找到就他那天個listenerID為listener->getListenerID();項加入到_listenerMap中。找到了就拿到這個ID的列表指針。
  3. 將要加入管理的偵聽器放到列表中。
  4. 根據加入的偵聽器的優先級別是不是0進行設置臟標記操作。
  5. 當優先級標記為0時肯定這個偵聽器是與場景顯示對象對象綁定的,找到這個綁定的Node對象與listener做了關聯,調用了associateNodeAndEventListener方法,將結點與偵聽器加入到了_nodeListenersMap列表里面。
  6. 因為偵聽器有了增加,所以原偵聽器列表就不是最新的了,cocos2d-x認為那就是臟數據,這樣設置了關於這個偵聽器ID的臟標記。

通過上述分析,我們可以進一步理解到EventDispatcher類內的幾個偵聽器列表變量的作用。

_listenerMap 用以偵聽器類型(就是偵聽器的ID)索引,值是一個數組,用來儲存偵聽同一偵聽器ID的所有偵聽器對象。

_priorityDirtyFlagMap 用來標記一類ID的偵聽器列表是對象是否有變化,偵是偵聽器ID,值為偵聽級別。

_nodeListenersMap 用來記錄結點類型數據的偵聽器列表,通俗點說就是以結點為索引所有偵聽的事件都存在這個map里面。

我們注意這里判斷了node->isRunning()屬性如果結點是在運行的結點,那么調用了resumeEventListenersForTarget方法。下面看下這個方法都做了些什么。

void EventDispatcher::resumeEventListenersForTarget(Node* target, bool recursive/* = false */)
{
    auto listenerIter = _nodeListenersMap.find(target);
    if (listenerIter != _nodeListenersMap.end())
    {
        auto listeners = listenerIter->second;
        for (auto& l : *listeners)
        {
            l->setPaused(false);
        }
    }
    setDirtyForNode(target);
    
    if (recursive)
    {
        const auto& children = target->getChildren();
        for (const auto& child : children)
        {
            resumeEventListenersForTarget(child, true);
        }
    }
}

這個函數兩個參數,第一個是目標結點對象,第二個參數是是否遞歸進行子對象調用。

這個函數過程,先在結點列表中找是否已經有這個結點了,找到之后將它的每個偵聽器的暫停狀態都 取消。然后設置這個結點為臟結點標記。

上面提到過設置臟的偵聽器,這里看一下設置臟結點函數。

void EventDispatcher::setDirtyForNode(Node* node)
{
    // Mark the node dirty only when there is an eventlistener associated with it. 
    if (_nodeListenersMap.find(node) != _nodeListenersMap.end())
    {
        _dirtyNodes.insert(node);
    }

    // Also set the dirty flag for node's children
    const auto& children = node->getChildren();
    for (const auto& child : children)
    {
        setDirtyForNode(child);
    }
}

這個函數雖然沒有遞歸參數來控制,但從實現 上來分析這經會遞歸 node結點的子結點,都設置成了臟結點。

這里出現了_dirtyNodes這個類成員變量,現在可以理解什么是臟結點了,就是偵聽器有變化的結點。

 

下面我們分析EventDispatcher類的另一個加入偵聽器的方法。

 

/** Adds a event listener for a specified event with the fixed priority.
     *  @param listener The listener of a specified event.
     *  @param fixedPriority The fixed priority of the listener.
     *  @note A lower priority will be called before the ones that have a higher value.
     *        0 priority is forbidden for fixed priority since it's used for scene graph based priority.
     */
    void addEventListenerWithFixedPriority(EventListener* listener, int fixedPriority);

從注釋我們先來一個整體的了解。

這個函數的作用是將一個指定優先級的偵聽器加入到管理列表里面。

這里強調了,0這個優先級不能被使用,因為這是顯示對象偵聽器優先級別。如果小於0的優先級那么這個偵聽器事件會在畫面渲染之前被觸發,大於0的優先級會在顯示對象渲染之后觸發事件回調。

好了,下面看實現過程。

void EventDispatcher::addEventListenerWithFixedPriority(EventListener* listener, int fixedPriority)
{
    CCASSERT(listener, "Invalid parameters.");
    CCASSERT(!listener->isRegistered(), "The listener has been registered.");
    CCASSERT(fixedPriority != 0, "0 priority is forbidden for fixed priority since it's used for scene graph based priority.");
    
    if (!listener->checkAvailable())
        return;
    
    listener->setAssociatedNode(nullptr);
    listener->setFixedPriority(fixedPriority);
    listener->setRegistered(true);
    listener->setPaused(false);

    addEventListener(listener);
}

與addEventListenerWithSceneGraphPriority方法大同小異,就是對listener進行了一些參數賦值,后面還是調用的addEventListener方法。這里就不多說了,值得注意的一點是,這個listener初始也是設置成暫停的,上面分析到在addEventListener調用后會將暫停狀態取消的。

 

繼續向下看,還有一個自定義事件的偵聽器注冊方法。

/** Adds a Custom event listener.
     It will use a fixed priority of 1.
     @return the generated event. Needed in order to remove the event from the dispather
     */
    EventListenerCustom* addCustomEventListener(const std::string &eventName, const std::function<void(EventCustom*)>& callback);

這個用戶自定義事件的偵聽器注冊方法。參數為一個事件名稱與一個回調函數。

從注釋里面可以了解這個偵聽器的優先級會被設置成1與就是在場景渲染之后事件才被處理。

EventListenerCustom* EventDispatcher::addCustomEventListener(const std::string &eventName, const std::function<void(EventCustom*)>& callback)
{
    EventListenerCustom *listener = EventListenerCustom::create(eventName, callback);
    addEventListenerWithFixedPriority(listener, 1);
    return listener;
}

這個函數返回了一個EventListenerCustom對象,並且通過實現過程可以看出這個listener返回的對象已經被注冊到了EventDispatcher管理列表里面。

看過了注冊偵聽器的方法,現在我們集中看一下注銷偵聽器的幾個重載方法。

void EventDispatcher::removeEventListener(EventListener* listener) // 注銷指定的偵聽器
{
    if (listener == nullptr)
        return;

    bool isFound = false;
    
    auto removeListenerInVector = [&](std::vector<EventListener*>* listeners){// 這里定義了一個匿名函數,作用是遍歷一個listener的數組查找是否含有指定的listener,將其從數組中刪除,釋放引用等操作。
        if (listeners == nullptr)
            return;
        
        for (auto iter = listeners->begin(); iter != listeners->end(); ++iter)
        {
            auto l = *iter;
            if (l == listener)
            {
                CC_SAFE_RETAIN(l);
                l->setRegistered(false);
                if (l->getAssociatedNode() != nullptr)
                {
                    dissociateNodeAndEventListener(l->getAssociatedNode(), l);
                    l->setAssociatedNode(nullptr);  // NULL out the node pointer so we don't have any dangling pointers to destroyed nodes.
                }
                
                if (_inDispatch == 0)
                {
                    listeners->erase(iter);
                    CC_SAFE_RELEASE(l);
                }
                
                isFound = true;
                break;
            }
        }
    };
    
    for (auto iter = _listenerMap.begin(); iter != _listenerMap.end();)// 遍歷_listenerMap 按偵聽器ID分類,一類一類遍歷
    {
        auto listeners = iter->second;
        auto fixedPriorityListeners = listeners->getFixedPriorityListeners();
        auto sceneGraphPriorityListeners = listeners->getSceneGraphPriorityListeners();

        removeListenerInVector(sceneGraphPriorityListeners); // 查找優先級為0的列表 
        if (isFound)
        {
            // fixed #4160: Dirty flag need to be updated after listeners were removed.
            setDirty(listener->getListenerID(), DirtyFlag::SCENE_GRAPH_PRIORITY); // 如果找到了,那么偵聽器對象有變化,設置臟標記。
        }
        else
        {
            removeListenerInVector(fixedPriorityListeners);// 查找優先級不為0的列表  
            if (isFound)
            {
                setDirty(listener->getListenerID(), DirtyFlag::FIXED_PRIORITY);
            }
        }
        
#if CC_NODE_DEBUG_VERIFY_EVENT_LISTENERS
        CCASSERT(_inDispatch != 0 ||
                 !sceneGraphPriorityListeners ||
                 std::count(sceneGraphPriorityListeners->begin(), sceneGraphPriorityListeners->end(), listener) == 0,
                 "Listener should be in no lists after this is done if we're not currently in dispatch mode.");
            
        CCASSERT(_inDispatch != 0 ||
                 !fixedPriorityListeners ||
                 std::count(fixedPriorityListeners->begin(), fixedPriorityListeners->end(), listener) == 0,
                 "Listener should be in no lists after this is done if we're not currently in dispatch mode.");
#endif

        if (iter->second->empty())// 如果列表已經沒有其它偵聽器那么刪除這個分類
        {
            _priorityDirtyFlagMap.erase(listener->getListenerID());
            auto list = iter->second;
            iter = _listenerMap.erase(iter);
            CC_SAFE_DELETE(list);
        }
        else
        {
            ++iter;
        }
        
        if (isFound)
            break;
    }

    if (isFound)// 找到了那么安全刪除這個偵聽器
    {
        CC_SAFE_RELEASE(listener);
    }
    else// 如果上面過程都沒找到,那么在將要注冊的偵聽器列表里面再找一遍,如果存在那么將其刪除。
    {
        for(auto iter = _toAddedListeners.begin(); iter != _toAddedListeners.end(); ++iter)
        {
            if (*iter == listener)
            {
                listener->setRegistered(false);
                listener->release();
                _toAddedListeners.erase(iter);
                break;
            }
        }
    }
}

下面我們看一下其它重載版本的注銷偵聽器的函數。

/** 根據偵聽器的大類型來刪除偵聽器 */
    void removeEventListenersForType(EventListener::Type listenerType);

    /** 根據Node結點來刪除偵聽器. */
    void removeEventListenersForTarget(Node* target, bool recursive = false);
    
    /** 根據指定事件名稱來刪除偵聽器 */
    void removeCustomEventListeners(const std::string& customEventName);

    /** 清除所有偵聽器 */
    void removeAllEventListeners();

這些方法我就不一個一個分析了,因為過程都與第一個版本的相似,就是查找,刪除,釋放引用 這幾個操作。大家可以自行看一下代碼。

再看一下偵聽器偵聽的暫停與恢復方法。

/** Pauses all listeners which are associated the specified target. */
    void pauseEventListenersForTarget(Node* target, bool recursive = false);
    
    /** Resumes all listeners which are associated the specified target. */
    void resumeEventListenersForTarget(Node* target, bool recursive = false);

這兩個方法也不用多說,就是設置了暫停屬性。第二個參數是用來指定是否遞歸作用於子結點的。

接下來我們看一下 事件分發的方法,

/** Dispatches the event
 *  Also removes all EventListeners marked for deletion from the
 *  event dispatcher list.
 */
void dispatchEvent(Event* event);


從注釋上初步了解這個函數是分發消息並且會注銷那些被標記為要刪除的偵聽器。

下面我們看實現。

void EventDispatcher::dispatchEvent(Event* event)
{
    if (!_isEnabled)
        return;
    
    updateDirtyFlagForSceneGraph();
    
    
    DispatchGuard guard(_inDispatch);
    
    if (event->getType() == Event::Type::TOUCH)
    {
        dispatchTouchEvent(static_cast<EventTouch*>(event));
        return;
    }
    
    auto listenerID = __getListenerID(event);
    
    sortEventListeners(listenerID);
    
    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();
        };
        
        dispatchEventToListeners(listeners, onEvent);
    }
    
    updateListeners(event);
}

這個函數的流程為:

  1. 調用 updateDirtyFlagForSceneGraph 這個函數我們后面再分析實現,在這里從命名上可以知道這塊處理了那些臟標記。
  2. 將TOUCH事件單獨進行了處理,也就將Touch事件調用了dispatchTouchEvent這個方法。這個方法后面我們也單獨分析。
  3. 把所有偵聽當前傳入的Event事件ID的偵聽器進行了排序。sortEventListeners方法。
  4. 針對每個偵聽當前事件的偵聽器進行分發,使用了dispatchEventToListeners方法。
  5. 調用updateListeners 這個方法也后面分析。

其實這個dispatchEvent只是做了一個分揀操作,並沒有直接去執行偵聽器的回調方法。

上面過程中提到了幾個重要的方法,我們下面一個一個分析。

updateDirtyFlagForSceneGraph

void EventDispatcher::updateDirtyFlagForSceneGraph()
{
    if (!_dirtyNodes.empty())
    {
        for (auto& node : _dirtyNodes)
        {
            auto iter = _nodeListenersMap.find(node);
            if (iter != _nodeListenersMap.end())
            {
                for (auto& l : *iter->second)
                {
                    setDirty(l->getListenerID(), DirtyFlag::SCENE_GRAPH_PRIORITY);
                }
            }
        }
        
        _dirtyNodes.clear();
    }
}

這個方法就是遍歷了_dirtyNodes列表,看看有沒有臟結點,一個一個的結點去設置新的事件優先級。最后將臟結點從_dirtyNodes里面刪除。

sortEventListeners

void EventDispatcher::sortEventListeners(const EventListener::ListenerID& listenerID)
{
    DirtyFlag dirtyFlag = DirtyFlag::NONE;
    
    auto dirtyIter = _priorityDirtyFlagMap.find(listenerID);
    if (dirtyIter != _priorityDirtyFlagMap.end())
    {
        dirtyFlag = dirtyIter->second;
    }
    
    if (dirtyFlag != DirtyFlag::NONE)
    {
        // Clear the dirty flag first, if `rootNode` is nullptr, then set its dirty flag of scene graph priority
        dirtyIter->second = DirtyFlag::NONE;

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

這個方法作用是根據指定的事件ID來對結點進行排序。

函數過程為:

  1. 在臟列表里面找這個listenerID
  2. 如果臟列表里有這個事件ID那么才進行排序,這里在臟列表里面找有一個優化,如果臟列表里面沒有,那么證明這類開事件沒有變化,那么就不用排序,因為上次已經排列過順序了。這塊這么處理是按需來排序。很巧妙。
  3. 下面根據優先級權限來分別調用了sortEventListenersOfFixedPriority與sortEventListenersOfSceneGraphPriority兩個方法。
  4. 這里要注意一點,在渲染對象中間傳遞事件實際上是以當前運行的場景為根結點來進行排序的。

我們再看下這兩個方法。

sortEventListenersOfFixedPriority

void EventDispatcher::sortEventListenersOfFixedPriority(const EventListener::ListenerID& listenerID)
{
    auto listeners = getListeners(listenerID);

    if (listeners == nullptr)
        return;
    
    auto fixedListeners = listeners->getFixedPriorityListeners();
    if (fixedListeners == nullptr)
        return;
    
    // 根據優先級排順序
    std::sort(fixedListeners->begin(), fixedListeners->end(), [](const EventListener* l1, const EventListener* l2) {
        return l1->getFixedPriority() < l2->getFixedPriority();
    });
    
    // 因為根據優先級排列順序為 <0   >0 下面這塊是找到第一個大於0優先級的索引。也就是分界點,優先級小於0的偵聽器在場景渲染之前觸發,大於0的偵聽器在場景渲染之后觸發。所以這個值很有用。
    int index = 0;
    for (auto& listener : *fixedListeners)
    {
        if (listener->getFixedPriority() >= 0)
            break;
        ++index;
    }
    
    listeners->setGt0Index(index);
    
#if DUMP_LISTENER_ITEM_PRIORITY_INFO
    log("-----------------------------------");
    for (auto& l : *fixedListeners)
    {
        log("listener priority: node (%p), fixed (%d)", l->_node, l->_fixedPriority);
    }    
#endif
    
}

 

sortEventListenersOfSceneGraphPriority

void EventDispatcher::sortEventListenersOfSceneGraphPriority(const EventListener::ListenerID& listenerID, Node* rootNode)
{
    auto listeners = getListeners(listenerID);
    
    if (listeners == nullptr)
        return;
    auto sceneGraphListeners = listeners->getSceneGraphPriorityListeners();
    
    if (sceneGraphListeners == nullptr)
        return;

    // Reset priority index
    _nodePriorityIndex = 0;
    _nodePriorityMap.clear();

    visitTarget(rootNode, true);
    
    // After sort: priority < 0, > 0
    std::sort(sceneGraphListeners->begin(), sceneGraphListeners->end(), [this](const EventListener* l1, const EventListener* l2) {
        return _nodePriorityMap[l1->getAssociatedNode()] > _nodePriorityMap[l2->getAssociatedNode()];
    });
    
#if DUMP_LISTENER_ITEM_PRIORITY_INFO
    log("-----------------------------------");
    for (auto& l : *sceneGraphListeners)
    {
        log("listener priority: node ([%s]%p), priority (%d)", typeid(*l->_node).name(), l->_node, _nodePriorityMap[l->_node]);
    }
#endif
}

這個場景渲染對象事件的優先級排列與上面一個函數過程類似,很好理解。不多說了。

dispatchEventToListeners 

void EventDispatcher::dispatchEventToListeners(EventListenerVector* listeners, const std::function<bool(EventListener*)>& onEvent)
{
    bool shouldStopPropagation = false;
    auto fixedPriorityListeners = listeners->getFixedPriorityListeners();
    auto sceneGraphPriorityListeners = listeners->getSceneGraphPriorityListeners();
    
    ssize_t i = 0;
    // 先處理 priority < 0 的事件偵聽器
    if (fixedPriorityListeners)
    {
        CCASSERT(listeners->getGt0Index() <= static_cast<ssize_t>(fixedPriorityListeners->size()), "Out of range exception!");
        
        if (!fixedPriorityListeners->empty())
        {
            for (; i < listeners->getGt0Index(); ++i)
            {
                auto l = fixedPriorityListeners->at(i);
                if (l->isEnabled() && !l->isPaused() && l->isRegistered() && onEvent(l))// 這里判斷事件是不是需要傳遞下去,前面是檢測偵聽器的狀態后面onEvent返回l是否停止的屬性。它是dispatcherEvent里一個匿名函數 .
                {
                    shouldStopPropagation = true;
                    break;
                }
            }
        }
    }
 // 處理 priority == 0 的事件偵聽器     
    if (sceneGraphPriorityListeners)
    {
        if (!shouldStopPropagation)
        {
            // priority == 0, scene graph priority
            for (auto& l : *sceneGraphPriorityListeners)
            {
                if (l->isEnabled() && !l->isPaused() && l->isRegistered() && onEvent(l))
                {
                    shouldStopPropagation = true;
                    break;
                }
            }
        }
    }
    // 處理 priority < 0 的事件偵聽器 
    if (fixedPriorityListeners)
    {
        if (!shouldStopPropagation)
        {
            // priority > 0
            ssize_t size = fixedPriorityListeners->size();
            for (; i < size; ++i)
            {
                auto l = fixedPriorityListeners->at(i);
                
                if (l->isEnabled() && !l->isPaused() && l->isRegistered() && onEvent(l))
                {
                    shouldStopPropagation = true;
                    break;
                }
            }
        }
    }
}

dispatchTouchEvent

這個函數大家可以自己看一下,這里不詳細分析了,基本過程與dispatchEventToListeners 差不多 區別在於它區分了onebyone及all by once的處理方式。

觸摸事件后繼章節我們會單獨分析。

 

updateListeners

void EventDispatcher::updateListeners(Event* event)
{
    CCASSERT(_inDispatch > 0, "If program goes here, there should be event in dispatch.");
    
    auto onUpdateListeners = [this](const EventListener::ListenerID& listenerID) // 這里定義了一個匿名函數,作用是清理_listenerMap 里的偵聽器,無效的都會進行注銷清除操作。
    {
        auto listenersIter = _listenerMap.find(listenerID);
        if (listenersIter == _listenerMap.end())
            return;

        auto listeners = listenersIter->second;
        
        auto fixedPriorityListeners = listeners->getFixedPriorityListeners();
        auto sceneGraphPriorityListeners = listeners->getSceneGraphPriorityListeners();
        
        if (sceneGraphPriorityListeners)
        {
            for (auto iter = sceneGraphPriorityListeners->begin(); iter != sceneGraphPriorityListeners->end();)
            {
                auto l = *iter;
                if (!l->isRegistered())
                {
                    iter = sceneGraphPriorityListeners->erase(iter);
                    l->release();
                }
                else
                {
                    ++iter;
                }
            }
        }
        
        if (fixedPriorityListeners)
        {
            for (auto iter = fixedPriorityListeners->begin(); iter != fixedPriorityListeners->end();)
            {
                auto l = *iter;
                if (!l->isRegistered())
                {
                    iter = fixedPriorityListeners->erase(iter);
                    l->release();
                }
                else
                {
                    ++iter;
                }
            }
        }
        
        if (sceneGraphPriorityListeners && sceneGraphPriorityListeners->empty())
        {
            listeners->clearSceneGraphListeners();
        }

        if (fixedPriorityListeners && fixedPriorityListeners->empty())
        {
            listeners->clearFixedListeners();
        }
    };// 匿名函數結束。

    
    if (event->getType() == Event::Type::TOUCH)// 調用匿名函數清除TOUCH類的無效偵聽器
    {
        onUpdateListeners(EventListenerTouchOneByOne::LISTENER_ID);
        onUpdateListeners(EventListenerTouchAllAtOnce::LISTENER_ID);
    }
    else// 調用匿名函數清除非TOUCH類型的無效偵聽器
    {
        onUpdateListeners(__getListenerID(event));
    }
    
    if (_inDispatch > 1)
        return;
    
    CCASSERT(_inDispatch == 1, "_inDispatch should be 1 here.");
    
    for (auto iter = _listenerMap.begin(); iter != _listenerMap.end();)// 清理listenerMap里的空項目。
    {
        if (iter->second->empty())
        {
            _priorityDirtyFlagMap.erase(iter->first);
            delete iter->second;
            iter = _listenerMap.erase(iter);
        }
        else
        {
            ++iter;
        }
    }
    
    if (!_toAddedListeners.empty())//清理_toAddedListeners里的空項目
    {
        for (auto& listener : _toAddedListeners)
        {
            forceAddEventListener(listener);
        }
        _toAddedListeners.clear();
    }
}

至此消息分發過程我們分析完了。

下面看一下用戶息定義的消息是怎么分發的

dispatchCustomEvent

void EventDispatcher::dispatchCustomEvent(const std::string &eventName, void *optionalUserData)
{
    EventCustom ev(eventName);
    ev.setUserData(optionalUserData);
    dispatchEvent(&ev);
}

參數為一個事件名稱和一個用戶自定義的數據指針,這里面創建了個文化EventCustom對象,然后調用了dispatchEvent(&ev);之后分發的過程與上面的一樣了。

 

好啊,今天又啰嗦這么多,主要分析了三個東東,Cocos2d-x中的  事件、 偵聽器、事件分發器

有經驗的同學可以看出,其實這里用到了一個常用的設計模式就是觀察者模式,采用了注冊事件,觸發采用回調函數來執行事件過程。

小魚在這里總結一下:

  1. Cocos2d-x的事件有幾種類型,觸摸(TOUCH)、鍵盤(KEYBOARD)、重力器(ACCELERATION)、鼠標(MOUSE)、用戶自定義類型(CUSTOM)
  2. 偵聽器也有幾種 TOUCH_ONE_BY_ONE, TOUCH_ALL_AT_ONCE, KEYBOARD, MOUSE, ACCELERATION, CUSTOM
  3. 分發器將事件對象傳遞給在分發器里注冊的事件偵聽對象,根據事件類型做匹配,匹配到合適的偵聽器后就算事件觸發了,調用 偵聽器的回調函數來執行事件過程。
  4. Cocos2d-x引擎中有一個分發器對象,就是在Direct類中的_eventDispatcher這個變量,在創建Direct對象時進行的初始化。

 

今天Cocos2d-x的事件分發機制源碼我們分析到這里,在event_dispatch目錄里還有一些關於事件的類我們就不做具體分析了,大同小異,如果理解上面的內容自行閱讀那部分源碼是沒問題的。

下一章 我們來閱讀Cocos2d-x3.0有關場景Scene類的源碼。


免責聲明!

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



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