这一章,我们来分析Cocos2d-x 事件机制相关的源码, 根据Cocos2d-x的工程目录,我们可以找到所有关于事件的源码都存在放在下图所示的目录中。
从这个event_dispatcher目录中的文件命名上分析 cocos2d-x与事件相关的类一共有四种, Event,EventListener,Eventdispatcher,touch分别为 事件,事件侦听器,事件分发器,触摸
我们先从Event类开始。
打开CCEvent.h文件
/** * Base class of all kinds of events. */class Event : public Ref{: enum class Type { touch,KEYBOARD,acceleration,MOUSE,CUSTOM }; protected* Constructor */ Event(Type type);* 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 bool isstopped() _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() { _currentTarget; }; * Sets current target voID setCurrentTarget(Node* target) { _currentTarget = target; }; Type _type; ///< Event type bool _isstopped; < whether the event has been stopped. Node* _currentTarget; < Current target frIEnd Eventdispatcher;};
这个类并且不复杂,先看一下类的注释,Event类是所有事件类的基类。
类定义的最上面有一个枚举,定义了事件的类型
事件的各类分别为 ,触摸事件, 键盘事件, 加速事件,鼠标事件, 用户自定义事件。
再看一下Event类的成员变量
_type 描述当前对象的事件类型。
_isstopped 描述当前事件是否已经停止
_currentTarget 是侦听事件的Node类型的对象
就这三个成员变量,含义很简单,Event类的几个方法也没什么特别的,就是成员变量的get set方法。
下面我们再看一下用户自定义事件 EventCustom 类的定义,来了解一下。
class EventCustom : Event{ EventCustom(const std::string& eventname); * Sets user data voID setUserData(voID* data) { _userData = data; }; * Gets user data voID* getUserData() _userData; }; * Gets event name string& getEventname() _eventname; };voID* _userData; < User datastd::string _eventname;};在自定义事件类中,多出了两个成员变量 一个是 _userData用来记录用户自定义数据,另一个_eventname用户给这个事件起的别名。
其它的关于Event的子类,大家可以自己看一下,内容都差不多。
下面我们来分析 EventListener 这个类。
这个类看着挺长,其实没什么内容,先看下类的定义。
EventListener也同样定义了一个类型
Type { UNKNowN,touch_ONE_BY_ONE,touch_ALL_AT_ONCE,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,255)">const std::function<voID(Event*)>& callback){ _onEvent = callback; _type = t; _ListenerID = ListenerID; _isRegistered = false; _paused = ; _isEnabled = ; return ;}
这个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 : EventListener{* 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(string& eventname,255)">voID(EventCustom*)>& callback); /// OverrIDes bool checkAvailable() overrIDe; virtual EventListenerCustom* clone() ; CC_CONSTRUCTOR_ACCESS: EventListenerCustom(); * Initializes event with type and callback function bool init(const ListenerID& ListenerID,0)"> callback); : std::function<voID(EventCustom*)> _onCustomEvent; frIEnd LuaEventListenerCustom;};EventListenerCustom又增加了一个成员变量,_onCustomEvent 接收一个EventCustom类型的事件为参数的回调函数。
我们可以看到,这个类的构造函数不是public形式的,所以这个类不能直接被实例化,
找到了EventListenerCustom提供了一个静态函数 create 所以实例化这个在的对象一定要使用这个create方法。
我们看一下create方法。
EventListenerCustom* EventListenerCustom::create( callback){ EventListenerCustom* ret = new EventListenerCustom(); if (ret && ret->init(eventname,callback)) { ret->autorelease(); } else { CC_SAFE_DELETE(ret); } ret;}
这个Create函数的结构,与Node的Create结构一样,新创建的EventListener加入到了autorelease列表里面,在Create的时候调用了init函数,我们再看一下EventListenerCustom::init方法。
bool EventListenerCustom::init( callback){ bool ret = ; _onCustomEvent = callback; auto Listener = [this](Event* event){ if (_onCustomEvent != nullptr) { _onCustomEvent(static_cast<EventCustom*>()); } }; if (EventListener::init(EventListener::Type::CUSTOM,ListenerID,Listener)) { ret = ; } ret;}这个函数也没什么特别的,但值得注意的是,CustomEvent的回调与基类的回调函数是怎么关联的。
在EventListenerCustom类中有一个成员变量_onCustomEvent它是一个函数指针来记录事件触发后的回调函数。
在EventListener类中也有一个_onEvent成员变量来记录事件触发的时候的回调函数。
EventListenerCustom::init函数中 有一个匿名函数,Listener 在这个匿名函数中 判断了_onCustomEvent是否为空,如果不为空那么调用_onCustomEvent。
后面调用基类的init方法,把这个匿名函数传递给了基类的_onEvent,这样基类的回调函数_onEvent与子类的_onCustomEvent就关联起来了。
下面我们看一下抽象方法在子类中怎么实现的
EventListenerCustom* EventListenerCustom::clone(){ EventListenerCustom* ret = init(_ListenerID,_onCustomEvent)) { ret-> ret;}
clone的 *** 作与Create方法很象,其实就是重新创建了一个对象,它的_ListenerID 、 _onCustomEvent值与原对象一样。返回的是新创建的对象指针。
事件类Event有了,侦听器类EventListener 有了,那么下面我们来分析事件的分发器Eventdispatcher。
打开CCEventdispatcher.h文件
*This class manages event Listener subscriptionsand event dispatching.The EventListener List is managed in such a way thatevent Listeners can be added and removed evenfrom within an EventListener,while events are beingdispatched.class Eventdispatcher : Ref{这个类也是Ref的子类,从注释上面我们先整体的了解Eventdispatcher的功能及作用。这个类管理事件侦听脚本及事件的分发处理。有一个事件侦听器列表,来记录所有侦听的事件。分析这个类我们首先还是从这个类的属性上开始。
/** 保存所有事件侦听器的列表 std::unordered_map<EventListener::ListenerID,EventListenerVector*> _ListenerMap; * 有脏标记的侦听器列表,具体什么是脏标记后面再分析。 _priorityDirtyFlagMap; * Node类型结点的事件侦听器列表 std::unordered_map<Node*,std::vector<EventListener*>*> _nodeListenersMap; * 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;* 优先级索引*/
_nodePriorityIndex; * 内部自定义侦听器索引*/
std::set<std::string> _internalCustomListenerIDs;
上面针对这个类的属性做了字面上的分析,大部分属性目前还不知道具体功能,不过没关系,后面我们一个一个去破解它的含义。
现在 我们来分析一下这个类的构造函数
Eventdispatcher::Eventdispatcher(): _indispatch(),_isEnabled(){ _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(),0)">The Listener has been registered.if (!Listener->checkAvailable()) ; Listener->setAssociatednode(node); Listener->setFixedPriority(); Listener->setRegistered(); addEventListener(Listener);}这个方法内容也简单,
1. 先检查了侦听器是否有效
2. 将结点与侦听器做了关联
3. 设置优先级为0,从注释上我们已经得到这个信息了,这个方法加入的侦听器都是显示对象的,所以优先级都为0
4. 设置侦听器已经注册状态
5. 调用了addEventListener方法,将侦听器加入到Eventdispatcher的侦听器列表里。
下面我们看一下addEventListener方法,了解是将侦听器加入到侦听器管理列表里的过程
voID Eventdispatcher::addEventListener(EventListener* Listener){ if (_indispatch == ) { forceAddEventListener(Listener); } { _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 = EventListenerVector(); _ListenerMap.insert(std::make_pair(ListenerID,Listeners)); } { Listeners = itr->second; } Listeners->push_back(Listener); if (Listener->getFixedPriority() == ) { setDirty(ListenerID,DirtyFlag::SCENE_GRAPH_PRIORITY); auto node = Listener->getAssociatednode(); CCASSERT(node != nullptr,0)">InvalID scene graph priority!); associateNodeAndEventListener(node,Listener); if (node->isRunning()) { resumeEventListenersForTarget(node); } } { setDirty(ListenerID,DirtyFlag::FIXED_PRIORITY); }}这个类里面涉及到了一个Eventdispatcher的内部类 EventListenerVector 这样一个数据结构,
这个结构在这里不多分析了,很简单,这个结构里封装了两个数组,_fixedListeners 与_sceneGraphListeners ,分别保存优先级不为0的侦听器指针与优先级为0的侦听器指针。
我们看一下强制将一个侦听器加入到管理列表的过程
_ListenerMap是按照侦听器ID来做分类的,每个侦听器ID都有一个EventListenerVector 数组。在_ListenerMap中找 ListenerID与要加入的Listener相同的侦听器列表 如果没找到就他那天个ListenerID为Listener->getListenerID();项加入到_ListenerMap中。找到了就拿到这个ID的列表指针。 将要加入管理的侦听器放到列表中。 根据加入的侦听器的优先级别是不是0进行设置脏标记 *** 作。 当优先级标记为0时肯定这个侦听器是与场景显示对象对象绑定的,找到这个绑定的Node对象与Listener做了关联,调用了associateNodeAndEventListener方法,将结点与侦听器加入到了_nodeListenersMap列表里面。 因为侦听器有了增加,所以原侦听器列表就不是最新的了,cocos2d-x认为那就是脏数据,这样设置了关于这个侦听器ID的脏标记。通过上述分析,我们可以进一步理解到Eventdispatcher类内的几个侦听器列表变量的作用。
_ListenerMap 用以侦听器类型(就是侦听器的ID)索引,值是一个数组,用来储存侦听同一侦听器ID的所有侦听器对象。
_priorityDirtyFlagMap 用来标记一类ID的侦听器列表是对象是否有变化,侦是侦听器ID,值为侦听级别。
_nodeListenersMap 用来记录结点类型数据的侦听器列表,通俗点说就是以结点为索引所有侦听的事件都存在这个map里面。
我们注意这里判断了node->isRunning()属性如果结点是在运行的结点,那么调用了resumeEventListenersForTarget方法。下面看下这个方法都做了些什么。
voID Eventdispatcher::resumeEventListenersForTarget(Node* target,255)">bool recursive = false ){ auto ListenerIter = _nodeListenersMap.find(target); if (ListenerIter != _nodeListenersMap.end()) { auto Listeners = ListenerIter->second; for (auto& l : *Listeners) { l->setPaused(); } } setDirtyForNode(target); (recursive) { const auto& children = target->getChildren(); for (const auto& child : children) { resumeEventListenersForTarget(child,); } }}这个函数两个参数,第一个是目标结点对象,第二个参数是是否递归进行子对象调用。
这个函数过程,先在结点列表中找是否已经有这个结点了,找到之后将它的每个侦听器的暂停状态都 取消。然后设置这个结点为脏结点标记。
上面提到过设置脏的侦听器,这里看一下设置脏结点函数。
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(); 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,255)">int fixedPriority);从注释我们先来一个整体的了解。
这个函数的作用是将一个指定优先级的侦听器加入到管理列表里面。
这里强调了,0这个优先级不能被使用,因为这是显示对象侦听器优先级别。如果小于0的优先级那么这个侦听器事件会在画面渲染之前被触发,大于0的优先级会在显示对象渲染之后触发事件回调。
好了,下面看实现过程。
voID Eventdispatcher::addEventListenerWithFixedPriority(EventListener* Listener,0)"> fixedPriority){ CCASSERT(Listener,); CCASSERT(fixedPriority != 0,0)">0 priority is forbIDden for fixed priority since it's used for scene graph based priority.setAssociatednode(nullptr); Listener->setFixedPriority(fixedPriority); Listener->setRegistered(); Listener->setPaused(); 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(string &eventname,255)">voID(EventCustom*)>& callback);这个用户自定义事件的侦听器注册方法。参数为一个事件名称与一个回调函数。
从注释里面可以了解这个侦听器的优先级会被设置成1与就是在场景渲染之后事件才被处理。
EventListenerCustom* Eventdispatcher::addCustomEventListener( callback){ EventListenerCustom *Listener = EventListenerCustom::create(eventname,callback); addEventListenerWithFixedPriority(Listener,128)">1); Listener;}
这个函数返回了一个EventListenerCustom对象,并且通过实现过程可以看出这个Listener返回的对象已经被注册到了Eventdispatcher管理列表里面。
看过了注册侦听器的方法,现在我们集中看一下注销侦听器的几个重载方法。
voID Eventdispatcher::removeEventListener(EventListener* Listener) 注销指定的侦听器{ if (Listener == nullptr) bool isFound = ; auto removeListenerInVector = [&](std::vector<EventListener*>* Listeners){ 这里定义了一个匿名函数,作用是遍历一个Listener的数组查找是否含有指定的Listener,将其从数组中删除,释放引用等 *** 作。 if (Listeners == nullptr) ; for (auto iter = Listeners->begin(); iter != Listeners->end(); ++iter) { auto l = *iter; if (l == Listener) { CC_SAFE_RETAIN(l); l->setRegistered(); 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. } ) { Listeners->erase(iter); CC_SAFE_RELEASE(l); } isFound = ; 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的列表 (isFound) { fixed #4160: Dirty flag need to be updated after Listeners were removed. setDirty(Listener->getListenerID(),DirtyFlag::SCENE_GRAPH_PRIORITY); 如果找到了,那么侦听器对象有变化,设置脏标记。 } { removeListenerInVector(fixedPriorityListeners); 查找优先级不为0的列表 (isFound) { setDirty(Listener->#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(),0)">);#endif if (iter->second->empty()) 如果列表已经没有其它侦听器那么删除这个分类 { _priorityDirtyFlagMap.erase(Listener->getListenerID()); auto List = iter->second; iter = _ListenerMap.erase(iter); CC_SAFE_DELETE(List); } { ++iter; } (isFound) ; } (isFound) 找到了那么安全删除这个侦听器 { CC_SAFE_RELEASE(Listener); } else 如果上面过程都没找到,那么在将要注册的侦听器列表里面再找一遍,如果存在那么将其删除。 { for(auto iter = _toAddedListeners.begin(); iter != _toAddedListeners.end(); ++iter) { if (*iter == Listener) { Listener->setRegistered(); Listener->release(); _toAddedListeners.erase(iter); ; } } }}下面我们看一下其它重载版本的注销侦听器的函数。
* 根据侦听器的大类型来删除侦听器 voID removeEventListenersForType(EventListener::Type ListenerType); * 根据Node结点来删除侦听器. voID removeEventListenersForTarget(Node* target,255)">bool recursive = * 根据指定事件名称来删除侦听器 voID removeCustomEventListeners( customEventname); * 清除所有侦听器 voID removeAllEventListeners();这些方法我就不一个一个分析了,因为过程都与第一个版本的相似,就是查找,删除,释放引用 这几个 *** 作。大家可以自行看一下代码。
再看一下侦听器侦听的暂停与恢复方法。
* Pauses all Listeners which are associated the specifIEd target. voID pauseEventListenersForTarget(Node* target,0)">* Resumes all Listeners which are associated the specifIEd target. voID resumeEventListenersForTarget(Node* target,255)">false);这两个方法也不用多说,就是设置了暂停属性。第二个参数是用来指定是否递归作用于子结点的。
接下来我们看一下 事件分发的方法,
* dispatches the event * Also removes all EventListeners marked for deletion from the * event dispatcher List. voID dispatchEvent(Event* event);
从注释上初步了解这个函数是分发消息并且会注销那些被标记为要删除的侦听器。
下面我们看实现。
voID Eventdispatcher::dispatchEvent(Event* ){ if (!_isEnabled) ; updateDirtyFlagForSceneGraph(); dispatchGuard guard(_indispatch); if (event->getType() == Event::Type::touch) { dispatchtouchEvent(static_cast<Eventtouch*>()); ; } auto ListenerID = __getListenerID(); sortEventListeners(ListenerID); auto iter =if (iter != _ListenerMap.end()) { auto Listeners = iter->second; auto onEvent = [&event](EventListener* Listener) -> { event->setCurrentTarget(Listener->getAssociatednode()); Listener->_onEvent(); event->isstopped(); }; dispatchEventToListeners(Listeners,onEvent); } updateListeners();}这个函数的流程为:
调用 updateDirtyFlagForSceneGraph 这个函数我们后面再分析实现,在这里从命名上可以知道这块处理了那些脏标记。 将touch事件单独进行了处理,也就将touch事件调用了dispatchtouchEvent这个方法。这个方法后面我们也单独分析。 把所有侦听当前传入的Event事件ID的侦听器进行了排序。sortEventListeners方法。 针对每个侦听当前事件的侦听器进行分发,使用了dispatchEventToListeners方法。 调用updateListeners 这个方法也后面分析。其实这个dispatchEvent只是做了一个分拣 *** 作,并没有直接去执行侦听器的回调方法。
上面过程中提到了几个重要的方法,我们下面一个一个分析。
updateDirtyFlagForSceneGraph
Eventdispatcher::updateDirtyFlagForSceneGraph(){ _dirtyNodes.empty()) { for (auto& node : _dirtyNodes) { auto iter = _nodeListenersMap.find(node); _nodeListenersMap.end()) { for (auto& l : *iter->second) { setDirty(l->这个方法就是遍历了_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 & ()DirtyFlag::FIXED_PRIORITY) { sortEventListenersOfFixedPriority(ListenerID); } )DirtyFlag::SCENE_GRAPH_PRIORITY) { auto rootNode = Director::getInstance()->getRunningScene(); (rootNode) { sortEventListenersOfSceneGraPHPriority(ListenerID,rootNode); } { dirtyIter->second = DirtyFlag::SCENE_GRAPH_PRIORITY; } } }}这个方法作用是根据指定的事件ID来对结点进行排序。
函数过程为:
在脏列表里面找这个ListenerID 如果脏列表里有这个事件ID那么才进行排序,这里在脏列表里面找有一个优化,如果脏列表里面没有,那么证明这类开事件没有变化,那么就不用排序,因为上次已经排列过顺序了。这块这么处理是按需来排序。很巧妙。 下面根据优先级权限来分别调用了sortEventListenersOfFixedPriority与sortEventListenersOfSceneGraPHPriority两个方法。 这里要注意一点,在渲染对象中间传递事件实际上是以当前运行的场景为根结点来进行排序的。我们再看下这两个方法。
sortEventListenersOfFixedPriority
voID Eventdispatcher::sortEventListenersOfFixedPriority( ListenerID){ auto Listeners = getListeners(ListenerID); ; auto fixedListeners = Listeners->getFixedPriorityListeners(); if (fixedListeners == 根据优先级排顺序std::sort(fixedListeners->begin(),fixedListeners->end(),[](const EventListener* l1,255)">const EventListener* l2) { return l1->getFixedPriority() < l2->getFixedPriority(); }); 因为根据优先级排列顺序为 <0 >0 下面这块是找到第一个大于0优先级的索引。也就是分界点,优先级小于0的侦听器在场景渲染之前触发,大于0的侦听器在场景渲染之后触发。所以这个值很有用。 int index = for (auto& Listener : *fixedListeners) { if (Listener->getFixedPriority() >= ) ; ++index; } Listeners->setGt0Index(index); #if DUMP_ListENER_ITEM_PRIORITY_INFO log(-----------------------------------fixedListeners) { log(Listener priority: node (%p),fixed (%d)",l->_node,l->_fixedPriority); } #endif }
sortEventListenersOfSceneGraPHPriority
voID Eventdispatcher::sortEventListenersOfSceneGraPHPriority(const EventListener::ListenerID& ListenerID,0)"> rootNode){ auto Listeners = getListeners(ListenerID); ; auto sceneGraphListeners = Listeners->getSceneGraPHPriorityListeners(); if (sceneGraphListeners == reset priority index _nodePriorityIndex = ; _nodePriorityMap.clear(); visitTarget(rootNode,0)"> After sort: priority < 0,> 0 std::sort(sceneGraphListeners->begin(),sceneGraphListeners->end(),[this](return _nodePriorityMap[l1->getAssociatednode()] > _nodePriorityMap[l2->getAssociatednode()]; }); sceneGraphListeners) { log(Listener priority: node ([%s]%p),priority (%d)_node]); }}这个场景渲染对象事件的优先级排列与上面一个函数过程类似,很好理解。不多说了。
dispatchEventToListeners
voID Eventdispatcher::dispatchEventToListeners(EventListenerVector* Listeners,255)">bool(EventListener*)>& onEvent){ bool shouldStopPropagation = ; auto fixedPriorityListeners = Listeners->getFixedPriorityListeners(); auto sceneGraPHPriorityListeners = Listeners->getSceneGraPHPriorityListeners(); ssize_t i = 先处理 priority < 0 的事件侦听器 (fixedPriorityListeners) { CCASSERT(Listeners->getGt0Index() <= static_cast<ssize_t>(fixedPriorityListeners->size()),0)">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 = ; ; } } } } // 处理 priority == 0 的事件侦听器 (sceneGraPHPriorityListeners) { shouldStopPropagation) { priority == 0,scene graph priority sceneGraPHPriorityListeners) { onEvent(l)) { shouldStopPropagation = ; } } } } 处理 priority < 0 的事件侦听器 (fixedPriorityListeners) { priority > 0 ssize_t size = fixedPriorityListeners->size(); for (; i < size; ++at(i); ; } } } }}dispatchtouchEvent
这个函数大家可以自己看一下,这里不详细分析了,基本过程与dispatchEventToListeners 差不多 区别在于它区分了onebyone及all by once的处理方式。
触摸事件后继章节我们会单独分析。
updateListeners
voID Eventdispatcher::updateListeners(Event* ){ CCASSERT(_indispatch > If program goes here,there should be event in dispatch.); auto onUpdateListeners = [ ListenerID) 这里定义了一个匿名函数,作用是清理_ListenerMap 里的侦听器,无效的都会进行注销清除 *** 作。 { auto ListenersIter = _ListenerMap.find(ListenerID); if (ListenersIter == _ListenerMap.end()) ; auto Listeners = ListenersIter->second; auto fixedPriorityListeners = Listeners->getSceneGraPHPriorityListeners(); (sceneGraPHPriorityListeners) { for (auto iter = sceneGraPHPriorityListeners->begin(); iter != sceneGraPHPriorityListeners->end();) { auto l = *iter; if (!l->isRegistered()) { iter = sceneGraPHPriorityListeners->erase(iter); l->release(); } { ++iter; } } } (fixedPriorityListeners) { for (auto iter = fixedPriorityListeners->begin(); iter != fixedPriorityListeners->isRegistered()) { iter = fixedPriorityListeners->if (sceneGraPHPriorityListeners && sceneGraPHPriorityListeners->empty()) { Listeners->clearSceneGraphListeners(); } if (fixedPriorityListeners && fixedPriorityListeners->clearFixedListeners(); } }; 匿名函数结束。 Event::Type::touch) 调用匿名函数清除touch类的无效侦听器 { onUpdateListeners(EventListenertouchOneByOne::ListENER_ID); onUpdateListeners(EventListenertouchAllAtOnce::ListENER_ID); } 调用匿名函数清除非touch类型的无效侦听器 { onUpdateListeners(__getListenerID()); } if (_indispatch > ) ; CCASSERT(_indispatch == 1,0)">_indispatch should be 1 here. 清理ListenerMap里的空项目。{ empty()) { _priorityDirtyFlagMap.erase(iter->first); delete iter-> _ListenerMap.erase(iter); } iter; } } _toAddedListeners.empty())//清理_toAddedListeners里的空项目
Listener : _toAddedListeners) { forceAddEventListener(Listener); } _toAddedListeners.clear(); }}
至此消息分发过程我们分析完了。
下面看一下用户息定义的消息是怎么分发的
dispatchCustomEvent
voID Eventdispatcher::dispatchCustomEvent(voID *optionalUserData){ EventCustom ev(eventname); ev.setUserData(optionalUserData); dispatchEvent(&ev);}参数为一个事件名称和一个用户自定义的数据指针,这里面创建了个文化EventCustom对象,然后调用了dispatchEvent(&ev);之后分发的过程与上面的一样了。
好啊,今天又啰嗦这么多,主要分析了三个东东,Cocos2d-x中的 事件、 侦听器、事件分发器
有经验的同学可以看出,其实这里用到了一个常用的设计模式就是观察者模式,采用了注册事件,触发采用回调函数来执行事件过程。
小鱼在这里总结一下:
Cocos2d-x的事件有几种类型,触摸(touch)、键盘(KEYBOARD)、重力器(acceleration)、鼠标(MOUSE)、用户自定义类型(CUSTOM) 侦听器也有几种 touch_ONE_BY_ONE,CUSTOM 分发器将事件对象传递给在分发器里注册的事件侦听对象,根据事件类型做匹配,匹配到合适的侦听器后就算事件触发了,调用 侦听器的回调函数来执行事件过程。 Cocos2d-x引擎中有一个分发器对象,就是在Direct类中的_eventdispatcher这个变量,在创建Direct对象时进行的初始化。今天Cocos2d-x的事件分发机制源码我们分析到这里,在event_dispatch目录里还有一些关于事件的类我们就不做具体分析了,大同小异,如果理解上面的内容自行阅读那部分源码是没问题的。
下一章 我们来阅读Cocos2d-x3.0有关场景Scene类的源码。
总结以上是内存溢出为你收集整理的Cocos2d-X3.0 刨根问底(七)----- 事件机制Event源码分析全部内容,希望文章能够帮你解决Cocos2d-X3.0 刨根问底(七)----- 事件机制Event源码分析所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)