Cocos2d-x引擎学习笔记(四)—— 事件分发 源码分析

cocos2d-x版本:3.17.2

运行环境:Visual Studio 2017

解决方案配置:Debug Win32


事件分发主要由三部分组成:

  • 事件分发器 EventDispatcher
  • 事件监听器 EventListener
  • 事件 Event

三者的关系可以理解为:在应用程序中事件可以看作是一个带有数据的实体,而事件监听器就是事情发生之后做一些处理工作的人派发事件就是将事件这个实体派发出去,告诉大家发生了这个事情,然后事件监听器会监听到事件触发,接收事件这个实体,取到事件中的数据,做相应的处理。

1. Event类

事件的基类Event

class CC_DLL Event : public Ref
{
public:
    /** Type Event type.*/
    enum class Type
    {
        TOUCH,
        KEYBOARD,
        ACCELERATION,
        MOUSE,
        FOCUS,
        GAME_CONTROLLER,
        CUSTOM
    };
    
CC_CONSTRUCTOR_ACCESS:
    /** Constructor */
    Event(Type type);
public:
    virtual ~Event();
    Type getType() const { return _type; }
    // 停止事件传播
    void stopPropagation() { _isStopped = true; }
    // 判断事件是否停止
    bool isStopped() const { return _isStopped; }  
    // 获取当前事件的目标
    Node* getCurrentTarget() { return _currentTarget; }
    
protected:
    // 设置当前的目标
    void setCurrentTarget(Node* target) { _currentTarget = target; }
    // 事件的类型
	Type _type;
    // 是否停止标志
    bool _isStopped;
    // 事件附加的目标结点
    Node* _currentTarget;
    // 友元类,事件分发器
    friend class EventDispatcher;
};

Event中的enum声明了7种事件类型,分别是触摸事件、键盘事件、加速计事件、鼠标事件、焦点事件、手柄事件和自定义事件,这些事件继承Event类,再额外添加自己相应的一些属性。

例如,这里的键盘事件EventKeyboard 就多定义了两个属性,_keyCode 表示按下的键值,_isPressed 表示是按下还是松开

class CC_DLL EventKeyboard : public Event
{
public:
    // function
protected:
	// 按下的键值
    KeyCode _keyCode;
    // 按下还是松开
    bool _isPressed;

    friend class EventListenerKeyboard;
};

2. EventListener类

该类负责监听事件的发生,并调用相应的回调函数来处理事件。

class CC_DLL EventListener : public Ref
{
public:
    /** Type Event type.*/
    enum class Type
    {
        UNKNOWN,
        TOUCH_ONE_BY_ONE,
        TOUCH_ALL_AT_ONCE,
        KEYBOARD,
        MOUSE,
        ACCELERATION,
        FOCUS,
		GAME_CONTROLLER,
        CUSTOM
    };

    typedef std::string ListenerID;

CC_CONSTRUCTOR_ACCESS:
    EventListener();

    // 初始化
    bool init(Type t, const ListenerID& listenerID, const std::function<void(Event*)>& callback);
public:
    virtual ~EventListener();
    // 检查当前监听器是否可用
    virtual bool checkAvailable() = 0;
    // 复制当前监听器
    virtual EventListener* clone() = 0;
    // 设置当前监听器的启用或禁用
    void setEnabled(bool enabled) { _isEnabled = enabled; }
    // 检查是否启用了侦听器
    bool isEnabled() const { return _isEnabled; }
    
protected:
    // 为监听器设置暂停状态
    void setPaused(bool paused) { _paused = paused; }
    // 判断监听器是否为暂停状态
    bool isPaused() const { return _paused; }
    // 标记该监听器被EventDispatcher注册
    void setRegistered(bool registered) { _isRegistered = registered; }
    // 检查该监听器是否被EventDispatcher注册
    bool isRegistered() const { return _isRegistered; }
    // 返回监听器的类型
    Type getType() const { return _type; }
    // 返回监听器的ID
    const ListenerID& getListenerID() const { return _listenerID; }
    // 为监听器设置fixed priority
    void setFixedPriority(int fixedPriority) { _fixedPriority = fixedPriority; }
    // 返回该监听器的fixed priority
    int getFixedPriority() const { return _fixedPriority; }
    // 设置该监听器关联的结点
    void setAssociatedNode(Node* node) { _node = node; }
    // 返回该监听器关联的结点
    Node* getAssociatedNode() const { return _node; }

    // 属性
    // 事件回调函数
    std::function<void(Event*)> _onEvent;
	// 监听器的类型
    Type _type;       
    // 监听器的ID
    ListenerID _listenerID;
    // 该监听器是否已经再EventDispatcher中注册
    bool _isRegistered;
	// 该数字越高,表示该优先级越大
    int   _fixedPriority;
    // 基于场景图的优先级
    Node* _node; 
    bool _paused; 
    bool _isEnabled;
    friend class EventDispatcher;
};
  • fixedPriority 表示事件的优先级,_node 表示事件依赖的场景结点,这两个属性是对立的,分别对应两种添加事件监听器的方式,一种是事件监听器依赖于场景结点,则需要保存这个结点(_node),这时事件不需要额外定义优先级;另一种是事件监听器注册在全局中,则需要定义事件的优先级(_fixedPriority)

  • 针对不同的Event,自然就有不同的EventListener,不同的监听器通过继承EventListener基类来实现不同的功能,其类型也是由enum Type所规定了。监听器的类型比事件类 多了两个,一个是UNKNOWN 另一个是将TOUCH 细分为两种,总的来说还是一一对应的。

  • 其中,init函数在子类初始化的时候会调用,而checkAvaliable方法则在注册事件监听器的时候调用,检查回调函数是否为空,如果为空,则不能注册监听器。

下面还是以键盘事件的监听器为例子进行分析:

class CC_DLL EventListenerKeyboard : public EventListener
{
public:
    static const std::string LISTENER_ID;
    
    /** Create a keyboard event listener.
     * 
     * @return An autoreleased EventListenerKeyboard object.
     */
    static EventListenerKeyboard* create();
    
    /// Overrides
    virtual EventListenerKeyboard* clone() override;
    virtual bool checkAvailable() override;
    
    std::function<void(EventKeyboard::KeyCode, Event*)> onKeyPressed;
    std::function<void(EventKeyboard::KeyCode, Event*)> onKeyReleased;
CC_CONSTRUCTOR_ACCESS:
    EventListenerKeyboard();
    bool init();
};

该类新定义了两个回调函数 onKeyPressed 和 onKeyReleased。这两个子回调函数将由父类定义的回调函数 _onEvent 调用。

bool EventListenerKeyboard::init()
{
    auto listener = [this](Event* event){
        auto keyboardEvent = static_cast<EventKeyboard*>(event);
        if (keyboardEvent->_isPressed)
        {
            if (onKeyPressed != nullptr)
                onKeyPressed(keyboardEvent->_keyCode, event);
        }
        else
        {
            if (onKeyReleased != nullptr)
                onKeyReleased(keyboardEvent->_keyCode, event);
        }
    };
    if (EventListener::init(Type::KEYBOARD, LISTENER_ID, listener))
    {
        return true;
    }
    return false;
}

该子类的init函数首先会定义新的两个回调函数 onKeyPressed 和 onKeyReleased,然后再调用父类的init函数。

而重载后的checkAvailable()则是检查这两个子回调函数是否为空

3. EventDispatcher类

EventDispatcher 的作用主要有两方面:

  • 第一个作用是管理事件监听器的订阅,包括注册监听器、移除监听器、暂停监听器、恢复监听器、设置监听器优先级;
  • 第二个作用就是派发事件,当一件事件发生时,由事件分发器派发出去,再由事件监听器接收处理。

EventDispatcher 定义了一个私有类 EventListenerVector 来存储注册的事件监听器,所有我们先看这个EventListenerVector私有类

// 存储两个SceneGraph与Fixed优先级的监听器
class EventListenerVector
{
    public:
    EventListenerVector();
    ~EventListenerVector();
    size_t size() const;
    bool empty() const;

    void push_back(EventListener* item);
    void clearSceneGraphListeners();
    void clearFixedListeners();
    void clear();

    std::vector<EventListener*>* getFixedPriorityListeners() const { return _fixedListeners; }
    std::vector<EventListener*>* getSceneGraphPriorityListeners() const { return _sceneGraphListeners; }
    ssize_t getGt0Index() const { return _gt0Index; }
    void setGt0Index(ssize_t index) { _gt0Index = index; }
    private:
    std::vector<EventListener*>* _fixedListeners;
    std::vector<EventListener*>* _sceneGraphListeners;
    ssize_t _gt0Index;
};

EventListenerVector 用两个向量Vector来保存监听器,分别对应两种监听器.

  • _sceneGraphListeners 保存的是事件优先级依赖于结点的监听器, _sceneGraphListeners 存储的监听器优先级数值都为 0,但其优先级还与依赖结点的绘制顺序(zOrder)有关。
  • _fixedListeners 保存的是独立设置事件优先级的监听器,数值越小优先级越高,_fixedListeners 存储的监听器优先级从 1 开始,1 表示最高优先级,对应的事件最先触发。
添加事件监听器
// SceneGraph优先级的事件监听器
void addEventListenerWithSceneGraphPriority(EventListener* listener, Node* node);
// Fixed优先级的事件监听器
void addEventListenerWithFixedPriority(EventListener* listener, int fixedPriority);
// 客户自定义的事件监听器
EventListenerCustom* addCustomEventListener(const std::string &eventName, const 
                                            std::function<void(EventCustom*)>& callback);

以其中一个为例

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

首先检查监听器(和结点)是否为空,检查监听器是否已注册(不能重复注册),检查监听器是否合法,即对应的回调函数是否有定义;

然后设置依赖的结点、优先级,设置为已注册,最后调用私有函数addEventListener注册事件监听器

void EventDispatcher::addEventListener(EventListener* listener)
{
    if (_inDispatch == 0)
    {
        forceAddEventListener(listener);
    }
    else
    {
        _toAddedListeners.push_back(listener);
    }
#if CC_ENABLE_GC_FOR_NATIVE_OBJECTS
    auto sEngine = ScriptEngineManager::getInstance()->getScriptEngine();
    if (sEngine)
    {
        sEngine->retainScriptObject(this, listener);
    }
#endif // CC_ENABLE_GC_FOR_NATIVE_OBJECTS
    listener->retain();
}

addEventListener 先判断事件分发器当前是否在派发事件,如果是的话则先将事件监听器放到一个队列中去,否则则调用 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 (std::nothrow) EventListenerVector();
        //将 EventListenerVector 保存在一个字典
        _listenerMap.emplace(listenerID, listeners);
    }
    else
    {
        listeners = itr->second;
    }
    
    listeners->push_back(listener);
    
    if (listener->getFixedPriority() == 0)
    {
        // 设置dirty flag
        setDirty(listenerID, DirtyFlag::SCENE_GRAPH_PRIORITY);
        
        auto node = listener->getAssociatedNode();
        CCASSERT(node != nullptr, "Invalid scene graph priority!");
        // 关联节点
        associateNodeAndEventListener(node, listener);
        
        if (!node->isRunning())
        {
            listener->setPaused(true);
        }
    }
    else
    {
        setDirty(listenerID, DirtyFlag::FIXED_PRIORITY);
    }
}
  • forceAddEventListener` 将监听器保存在一个 EventListenerVector 中
  • 再将这个 EventListenerVector 保存在一个字典 _listenerMap 中
  • 再设置一个 dirty flag
  • 最后设置一下关联结点

_listenerMap 是监听器ID-EventListenerVector 键值对,也就是同一类型(ID)的监听器存储在一个 EventListenerVector 中,而这些监听器可以注册在结点上,也可能独立注册,EventListenerVector 会将它们放在不同的向量列表中,看一下它的 push_back 方法

void EventDispatcher::EventListenerVector::push_back(EventListener* listener)
{
#if CC_NODE_DEBUG_VERIFY_EVENT_LISTENERS
    CCASSERT(_sceneGraphListeners == nullptr ||
             std::count(_sceneGraphListeners->begin(), _sceneGraphListeners->end(), listener) == 0,
             "Listener should not be added twice!");
        
    CCASSERT(_fixedListeners == nullptr ||
             std::count(_fixedListeners->begin(), _fixedListeners->end(), listener) == 0,
             "Listener should not be added twice!");
#endif
	// secneGraph优先级的监听器
    if (listener->getFixedPriority() == 0)
    {
        if (_sceneGraphListeners == nullptr)
        {
            _sceneGraphListeners = new (std::nothrow) std::vector<EventListener*>();
            _sceneGraphListeners->reserve(100);
        }
        
        _sceneGraphListeners->push_back(listener);
    }
    // fixed优先级的监听器
    else
    {
        if (_fixedListeners == nullptr)
        {
            _fixedListeners = new std::vector<EventListener*>();
            _fixedListeners->reserve(100);
        }
        
        _fixedListeners->push_back(listener);
    }
}

上面说完了forceAddEventListener部分,那么另一部分加入到队列_toAddedListeners中,后面会接着什么操作呢。实际上,为了保证逻辑的正确性,EventDispatcher 在派发事件过程中是不能注册事件监听器,故将这些事件暂时放在了_toAddedListeners中,之后等到分发器空闲的时候会调用updateListeners来注册这些暂时被挂起的监听器。

void EventDispatcher::updateListeners(Event* event)
{
    // updateListeners 会根据事件类型更新这一事件监听器 ID 对应的所有监听器
    ······
    
    // 将之前挂起的监听器重新注册
    if (!_toAddedListeners.empty())
    {
        for (auto& listener : _toAddedListeners)
        {
            forceAddEventListener(listener);
        }
        _toAddedListeners.clear();
    }

    if (!_toRemovedListeners.empty())
    {
        cleanToRemovedListeners();
    }
}

遍历 _toAddedListeners 列表,调用 forceAddEventListener 注册这些挂起的监听器;

而后面的_toRemovedListeners与添加监听器一样,临时挂起不能及时移除的监听器,到update的时候才遍历进行移除。

移除事件监听器
void removeEventListener(EventListener* listener);
void removeEventListenersForListenerID(const EventListener::ListenerID& listenerID);

void removeEventListenersForType(EventListener::Type listenerType);
void removeEventListenersForTarget(Node* target, bool recursive = false);
void removeCustomEventListeners(const std::string& customEventName);
void removeAllEventListeners();

其中最为关键的函数是removeEventListener

void EventDispatcher::removeEventListener(EventListener* listener)
{
    if (listener == nullptr)
        return;
    
    // just return if listener is in _toRemovedListeners to avoid remove listeners more than once
    if (std::find(_toRemovedListeners.begin(), _toRemovedListeners.end(), listener) != _toRemovedListeners.end())
        return;

    bool isFound = false;
    // 遍历 _listenerMap 得到一个监听器 ID 对应的监听器列表 listeners
    auto removeListenerInVector = [&](std::vector<EventListener*>* listeners){
        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);  // nullptr out the node pointer so we don't have any dangling pointers to destroyed nodes.
                }
                
                if (_inDispatch == 0)
                {
                    iter = listeners->erase(iter);
                    releaseListener(l);
                }
                else
                {
                    _toRemovedListeners.push_back(l);
                }
                
                isFound = true;
                break;
            }
        }
    };
    
    for (auto iter = _listenerMap.begin(); iter != _listenerMap.end();)
    {
        // 分别遍历 listeners 的两个子列表 fixedPriorityListeners
        // sceneGraphPriorityListeners 里的监听器
        auto listeners = iter->second;
        auto fixedPriorityListeners = listeners->getFixedPriorityListeners();
        auto sceneGraphPriorityListeners = listeners->getSceneGraphPriorityListeners();

        removeListenerInVector(sceneGraphPriorityListeners);
        if (isFound)
        {
            // fixed #4160: Dirty flag need to be updated after listeners were removed.
            setDirty(listener->getListenerID(), DirtyFlag::SCENE_GRAPH_PRIORITY);
        }
        else
        {
            removeListenerInVector(fixedPriorityListeners);
            if (isFound)
            {
                // 设置为未注册,取消监听器和结点的关联关系
                setDirty(listener->getListenerID(), DirtyFlag::FIXED_PRIORITY);
            }
        }
        
        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;
    }
	// step3 将监听器从列表移除后,该列表可能为空了,即没有该监听器 ID 对应的监听器了,所以要把这个列表从 _listenerMap 移除
    if (isFound)
    {
        releaseListener(listener);
    }
    else
    {
        for(auto iter = _toAddedListeners.begin(); iter != _toAddedListeners.end(); ++iter)
        {
            if (*iter == listener)
            {
                listener->setRegistered(false);
                releaseListener(listener);
                _toAddedListeners.erase(iter);
                break;
            }
        }
    }
}
  • 遍历 _listenerMap 得到一个监听器 ID 对应的监听器列表 listeners,再分别遍历 listeners 的两个子列表 fixedPriorityListeners 和 sceneGraphPriorityListeners 里的监听器,如果找到要移除的监听器则进行移除,遍历结束
  • 移除操作和注册操作相对应,设置为未注册,取消监听器和结点的关联关系,然后将监听器从列表中移除,要注意的是和注册监听器一样,如果正在派发事件则不能进行移除操作,而是将监听器暂时放在一个列表 _toRemovedListeners 中,然后等事件派发完成调用 updateListeners 进行移除
  • 将监听器从列表移除后,该列表可能为空了,即没有该监听器 ID 对应的监听器了,所以要把这个列表从 _listenerMap 移除
  • 如果遍历完 _listenerMap 都没有找到要移除的监听器,则遍历 _toAddedListeners 列表,看看该监听器是否在注册的时候被挂起,如果找到了则从 _toAddedListeners 中移除,这样这个挂起的监听器就不会在后面被注册了
分发事件
void EventDispatcher::dispatchEvent(Event* event)
{
    if (!_isEnabled)
        return;
    
    updateDirtyFlagForSceneGraph();
    
	// _inDispatch用于表示当前正有多少事件正在分发。
    DispatchGuard guard(_inDispatch);
    
    // TOUCH事件要另外处理,因为有两种情况
    if (event->getType() == Event::Type::TOUCH)
    {
        dispatchTouchEvent(static_cast<EventTouch*>(event));
        return;
    }
    //  根据事件类型得到事件监听器类型
    auto listenerID = __getListenerID(event);
    // 排序 listener,找出优先级最高的
    sortEventListeners(listenerID);
    
    auto pfnDispatchEventToListeners = &EventDispatcher::dispatchEventToListeners;
    // 鼠标事件也看作TOUCH来处理
    if (event->getType() == Event::Type::MOUSE) {
        pfnDispatchEventToListeners = &EventDispatcher::dispatchTouchEventToListeners;
    }
    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);
}
  • 得到事件监听器类型 listenerID 之后,先调用 sortEventListeners 方法对该类型的所有监听器进行排序,排序的依据是优先级;
  • 然后再调用 dispatchEventToListeners 方法,这个方法会找到最先响应的监听器,调用其 _onEvent 方法,完成事件回调;
  • 触摸事件因为有单点触摸和多点触摸,所以特殊处理,调用的是 dispatchTouchEventToListeners 方法,而鼠标事件在引擎中也被看成是触摸事件,所以也是调用的 dispatchTouchEventToListeners 方法。

在监听器的新建与删除中有很多涉及到了Dirty Flag,这个脏的标记的作用是监听器列表需要重新排序。代码中可以看到,这个 DirtyFlag 是设置给 ListenerID 的,每当新添加一个 Listener,或则删除一个 Listener 的时候,就会给当前 ListenerListenerID 添加一个 DirtyFlag。说明这个脏是指 ListenerID 对应的监听器向量列表需要重新排序了,如果不脏就不需要排序。

你可能感兴趣的:(Cocos2d引擎)