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 = ;
for (auto& listener : *fixedListeners)
{
if (listener->getFixedPriority() >= )
break;
++index;
} listeners->setGt0Index(index);
4.2 sortEventListenersOfSceneGraphPriority(listenerID, rootNode)
参数rootNode是当前运行的场景。
同上面的排序一样,显先获取容器。不同之处在于sceneGraphListeners容器里的监听器优先级都为0,排序需要按照node的顺序。
需要_nodePriorityIndex容器记录node的优先级。
_nodePriorityIndex = ;
_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 > )
{
Node* child = nullptr;
// visit children zOrder < 0
for( ; i < childrenCount; i++ )
{
child = children.at(i); if ( child && child->getLocalZOrder() < )
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为空的元素。
Cocos2d-x 学习笔记(15.2) EventDispatcher 事件分发机制 dispatchEvent(event)的更多相关文章
- Cocos2d-x 学习笔记(15.4) EventDispatcher 事件分发具体逻辑 dispatchEventToListeners函数
dispatchEvent(Event* event)方法在对事件对应的监听器进行重新排序后,进行事件分发操作.具体操作由dispatchEventToListeners方法执行. 该方法声明: vo ...
- Cocos2d-x 3.2 学习笔记(九)EventDispatcher事件分发机制
EventDispatcher事件分发机制先创建事件,注册到事件管理中心_eventDispatcher,通过发布事件得到响应进行回调,完成事件流. 有五种不同的事件机制:EventListenerT ...
- Cocos2d-x 学习笔记(15.1) EventDispatcher
EventDispatcher对监听器进行管理,围绕着监听器工作.可以添加.删除.暂停/恢复监听器.分发事件到监听器. 1. 一些成员 /** 把ListenerID和同ID监听器的容器对应 */ s ...
- Cocos2d-x 学习笔记(15.3) EventDispatcher DirtyFlag 脏标记
1. 定义 用枚举定义脏标记的4种类型. enum class DirtyFlag { NONE = , FIXED_PRIORITY = << , SCENE_GRAPH_PRIORIT ...
- android开发艺术探索读书笔记之-------view的事件分发机制
View的点击事件的分发,其实就是对MotionEvent事件的分发过程,即当一个MotionEvent产生后,系统需要把这个事件传递给一个具体的View,而这个过程就是分发过程. 分发过程主要由以下 ...
- android开发学习 ------- 【转】 android事件分发机制 和 自定义view涉及的事件分发
参考 https://blog.csdn.net/carson_ho/article/details/54136311 ,写的很完美,原理入门的一篇博客,看这一篇就够了 https://www. ...
- Ext.Net学习笔记15:Ext.Net GridPanel 汇总(Summary)用法
Ext.Net学习笔记15:Ext.Net GridPanel 汇总(Summary)用法 Summary的用法和Group一样简单,分为两步: 启用Summary功能 在Feature标签内,添加如 ...
- SQL反模式学习笔记15 分组
目标:查询得到每组的max(或者min等其他聚合函数)值,并且得到这个行的其他字段 反模式:引用非分组列 单值规则:跟在Select之后的选择列表中的每一列,对于每个分组来说都必须返回且仅返回一直值. ...
- 并发编程学习笔记(15)----Executor框架的使用
Executor执行已提交的 Runnable 任务的对象.此接口提供一种将任务提交与每个任务将如何运行的机制(包括线程使用的细节.调度等)分离开来的方法.通常使用 Executor 而不是显式地创建 ...
随机推荐
- Java位运算实现加减乘除四则运算
本文是继<一文了解有趣的位运算>的第二篇文章. 我们知道,计算机最基本的操作单元是字节(byte),一个字节由8个位(bit)组成,一个位只能存储一个0或1,其实也就是高低电平.无论多么复 ...
- Java 教程 (Java 对象和类)
Java 对象和类 Java作为一种面向对象语言.支持以下基本概念: 多态 继承 封装 抽象 类 对象 实例 方法 重载 本节我们重点研究对象和类的概念. 对象:对象是类的一个实例(对象不是找个女朋友 ...
- [VB.NET Tips]再谈字符串连接之内置池
CLR自动维护一个称为"内置池"(暂存池)(intern pool)的表,在编译时此表包含程序中声明的每个唯一的字符串常量的单个实例,以及以编程方式创建的String类的任何唯一实 ...
- Python + opencv 实现图片文字的分割
实现步骤: 1.通过水平投影对图形进行水平分割,获取每一行的图像: 2.通过垂直投影对分割的每一行图像进行垂直分割,最终确定每一个字符的坐标位置,分割出每一个字符: 先简单介绍一下投影法:分别在水平和 ...
- 简述python的turtle绘画命令及解释
一 基础认识 turtle库是python的标准库之一,它是一个直观有趣的图形绘制数据库,turtle(海龟)图形绘制的概念诞生1969年.它的应用十分广,而且使用简单,只要在编写python程序时写 ...
- FTP无法登录问题-内有网盘福利
http://dinghuqiang.blog.51cto.com/19871/701185/ 一顿操作猛如虎,还是没解决. 然后我想想,会不会是浏览器缓存问题? 换个火狐登录看看! 哇擦!看到报错了 ...
- MySQL在Linux系统环境的安装和无主机登录配置
将mysql 安装在单个Linux系统主机,并配置本地或远程(此处可仅单指局域状态下的环境)的无主机登录.谨记的是:操作完mysql 设置时,需以flush privileges进行权限 ...
- OpenGl 导入读取多个3D模型 并且添加鼠标控制移动旋转
原文作者:aircraft 原文链接:https://www.cnblogs.com/DOMLX/p/11627508.html 前言: 因为接下来的项目需求是要读取多个3D模型,并且移动拼接,那么我 ...
- MongoDB 学习笔记之 手动预先分片
手动预先分片: 目的:手动预先分片是为了防止未来chunk的移动,减少IO. sh.shardCollection("shop.users",{"userId" ...
- C# WinForm 跨线程访问控件(实用简洁写法)
C# WinForm 跨线程访问控件(实用简洁写法) 1.<C# WinForm 跨线程访问控件(实用简洁写法)> 2.<基于.NET环境,C#语言 实现 TCP NAT ...