Cocos
的事件分發(fā)機(jī)制祖能,怎么說呢歉秫,總感覺有些亂蛾洛,借此整理一下养铸。先看看與事件分發(fā)相關(guān)的類。
事件相關(guān)的類
Event 相關(guān)類, 分發(fā)事件的中的事件:
Event
(基類),EventCustom
(自定義事件),EventTouch
(觸摸事件),EventMouse
(鼠標(biāo)事件),EventKeyboard
(鍵盤事件),EventFocus
(控件獲取焦點(diǎn)事件),EventAcceleration
(加速計(jì)事件)
事件監(jiān)聽器:
EventListener
,EventListenerCustom
,EventListenerFocus
,EventListenerMouse
,EventListenerTouch
,EventListenerKayboard
,EventListenerAcceleration
事件ID
ListenerID
(事件的區(qū)分標(biāo)志轧膘,其實(shí)就是std::string
)
事件分發(fā)器:
EventDispatcher
(事件分發(fā)機(jī)制邏輯集合體)
創(chuàng)建事件
創(chuàng)建事件就簡單的new
一個(gè)Event
的子類即可钞螟。
事件監(jiān)聽器的創(chuàng)建與監(jiān)聽
事件監(jiān)聽器,也就是說EventListener
谎碍。添加事件監(jiān)聽器有三個(gè)方法鳞滨,都在EventDispatch
中,分別是:
/** 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);
/** 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);
/** Adds a Custom event listener.
It will use a fixed priority of 1.
* @param eventName A given name of the event.
* @param callback A given callback method that associated the event name.
* @return the generated event. Needed in order to remove the event from the dispatcher
*/
EventListenerCustom* addCustomEventListener(const std::string &eventName, const std::function<void(EventCustom*)>& callback);
事件分發(fā)的時(shí)候主要有兩種優(yōu)先級(jí)蟆淀。
第一種是:ScenePriority
第二種是:FixedPriority
仔細(xì)跳到addEventListenerWithSceneGraphPriority
函數(shù)去看看會(huì)發(fā)現(xiàn):
void EventDispatcher::addEventListenerWithSceneGraphPriority(EventListener* listener, Node* node)
{
...
// 設(shè)置優(yōu)先級(jí)
listener->setFixedPriority(0);
...
addEventListener(listener);
}
EventListenerCustom* EventDispatcher::addCustomEventListener(const std::string &eventName, const std::function<void(EventCustom*)>& callback)
{
EventListenerCustom *listener = EventListenerCustom::create(eventName, callback);\
// 設(shè)置優(yōu)先級(jí)
addEventListenerWithFixedPriority(listener, 1);
return listener;
}
其實(shí)事件監(jiān)聽器保存的優(yōu)先級(jí)其實(shí)只有 FixPriority拯啦。
從
EventListener
這個(gè)類的方法中也可以看出來,里面涉及到保存優(yōu)先級(jí)的只有setFixedPriority
這一個(gè)函數(shù)熔任。
說到優(yōu)先級(jí)褒链,那么事件分發(fā)的時(shí)候是怎么處理這些優(yōu)先級(jí)的呢?官網(wǎng)只有寫 SceneGraphPriority 的優(yōu)先級(jí)是怎么處理的疑苔,那么 FixPriority 的優(yōu)先程度和數(shù)值是什么關(guān)系甫匹,這就去偷窺內(nèi)部了。
事件分發(fā)優(yōu)先級(jí)順序:
我們來脫掉 EventDispatcher
的衣服看看:
void EventDispatcher::dispatchEvent(Event* event)
{
...
// 先通過event獲取到事件的標(biāo)志ListenerID
auto listenerID = __getListenerID(event);
// 排序此事件的所有的監(jiān)聽器
sortEventListeners(listenerID);
// 分發(fā)事件邏輯的函數(shù)指針
auto pfnDispatchEventToListeners = &EventDispatcher::dispatchEventToListeners;
if (event->getType() == Event::Type::MOUSE) {
// 如果是鼠標(biāo)事件重新賦值分發(fā)事件的函數(shù)指針
pfnDispatchEventToListeners = &EventDispatcher::dispatchTouchEventToListeners;
}
// 獲取改事件的所有的監(jiān)聽器
auto iter = _listenerMap.find(listenerID);
if (iter != _listenerMap.end())
{
// 如果有,取出里面監(jiān)聽器的Vector
auto listeners = iter->second;
// 找到對(duì)應(yīng)的監(jiān)聽器的時(shí)候會(huì)觸發(fā)的回調(diào)函數(shù)
auto onEvent = [&event](EventListener* listener) -> bool{
event->setCurrentTarget(listener->getAssociatedNode());
// 觸發(fā)onEvent回調(diào)
listener->_onEvent(event);
return event->isStopped();
};
// 調(diào)用函數(shù)指針分發(fā)事件
(this->*pfnDispatchEventToListeners)(listeners, onEvent);
}
...
}
偷窺代碼分析后發(fā)現(xiàn)這段代碼并沒有詳細(xì)指出分發(fā)事件的時(shí)候的優(yōu)先級(jí)兵迅。仔細(xì)想想應(yīng)該是 sortEventListener
和 pfnDispatchEventToListeners
中在搞鬼抢韭。
先分析 sortEventListener
,脫掉她的胖次去里面看看恍箭。
void EventDispatcher::sortEventListeners(const EventListener::ListenerID& listenerID)
{
...
...
if ((int)dirtyFlag & (int)DirtyFlag::FIXED_PRIORITY)
{
// 對(duì)FixPriority優(yōu)先級(jí)類型的監(jiān)聽器排序
sortEventListenersOfFixedPriority(listenerID);
}
if ((int)dirtyFlag & (int)DirtyFlag::SCENE_GRAPH_PRIORITY)
{
auto rootNode = Director::getInstance()->getRunningScene();
if (rootNode)
{
// 對(duì)SceneGraphPriority類型的監(jiān)聽器排序
sortEventListenersOfSceneGraphPriority(listenerID, rootNode);
}
...
}
}
}
void EventDispatcher::sortEventListenersOfFixedPriority(const EventListener::ListenerID& listenerID)
{
auto listeners = getListeners(listenerID);
...
// After sort: priority < 0, > 0 排序刻恭,根據(jù)FixedPriority的數(shù)值大小,越小的優(yōu)先級(jí)越高
std::sort(fixedListeners->begin(), fixedListeners->end(), [](const EventListener* l1, const EventListener* l2) {
return l1->getFixedPriority() < l2->getFixedPriority();
});
...
}
void EventDispatcher::sortEventListenersOfSceneGraphPriority(const EventListener::ListenerID& listenerID, Node* rootNode)
{
auto listeners = getListeners(listenerID);
...
auto sceneGraphListeners = listeners->getSceneGraphPriorityListeners();
...
// Reset priority index
_nodePriorityIndex = 0;
_nodePriorityMap.clear();
// 從當(dāng)前根節(jié)點(diǎn)rootNode開始扯夭,遍歷整棵節(jié)點(diǎn)樹吠各,并標(biāo)記上相應(yīng)的節(jié)點(diǎn)等級(jí),父節(jié)點(diǎn)的優(yōu)先級(jí)比子節(jié)點(diǎn)的大
visitTarget(rootNode, true);
// After sort: priority < 0, > 0 因?yàn)镾ceneGraphPriority都是FixPriority為0的事件類型勉抓,所以比較節(jié)點(diǎn)在渲染書中的優(yōu)先級(jí)
std::sort(sceneGraphListeners->begin(), sceneGraphListeners->end(), [this](const EventListener* l1, const EventListener* l2) {
return _nodePriorityMap[l1->getAssociatedNode()] > _nodePriorityMap[l2->getAssociatedNode()];
});
...
}
上面寫了兩種優(yōu)先級(jí)的排序贾漏,一種是 FixPriority,優(yōu)先級(jí)根據(jù) fixedPriority 的數(shù)值從小往大排序藕筋、另一種是 SceneGraphPriority纵散,根據(jù)節(jié)點(diǎn)在渲染樹種的優(yōu)先級(jí)排序,具體怎么樣官網(wǎng)有解釋隐圾,這里不做展開伍掀。
值的注意的是 sortEventListener
的時(shí)候,判斷當(dāng)前 ListenerID
的類型是用位標(biāo)記來判斷的暇藏,一個(gè) int
類型的 flag蜜笤。也就是說明一個(gè) listenerID
既可以是 SceneGraphPriority 也可以是 FixedPriority,那么實(shí)際分發(fā)的時(shí)候這兩個(gè)的優(yōu)先級(jí)怎么排盐碱?答案就在 pfnDispatchEventToListeners
中
pfnDispatchEventToListeners
可以指向兩個(gè)方法:dispatchEventToListeners
和 dispatchTouchEventToListeners
把兔。其中兩個(gè)方法除了分發(fā)SceneGraphPriority
的時(shí)候不一樣外,其他的一樣瓮顽,為了方便起見县好,這里只分析 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 優(yōu)先處理priority小于0的時(shí)候的事件監(jiān)聽器
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);
// 判斷是否可以執(zhí)行事件暖混,如果可以最后調(diào)用onEvent執(zhí)行缕贡,如果onEvent返回true,說明吞噬事件拣播,結(jié)束分發(fā)晾咪。
if (l->isEnabled() && !l->isPaused() && l->isRegistered() && onEvent(l))
{
shouldStopPropagation = true;
break;
}
}
}
}
// 接下來分發(fā)SceneGraphPriority的事件
if (sceneGraphPriorityListeners)
{
// 判斷事件是否已經(jīng)終止發(fā)送
if (!shouldStopPropagation)
{
// priority == 0, scene graph priority
for (auto& l : *sceneGraphPriorityListeners)
{
if (l->isEnabled() && !l->isPaused() && l->isRegistered() && onEvent(l))
{
shouldStopPropagation = true;
break;
}
}
}
}
// 最后分發(fā)到fixedPriority > 0 的監(jiān)聽器
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;
}
}
}
}
}
這里面清楚的寫出了事件分發(fā)時(shí)候的邏輯處理,先分發(fā)事件到 fixedPriority < 0 的監(jiān)聽器中贮配,然后再分發(fā)到 = 0 的監(jiān)聽器(SceneGraphPriority)中谍倦,最后在分發(fā)到 > 0 的監(jiān)聽器中,如果中途出現(xiàn) onEvent
返回為 true
的結(jié)果牧嫉,則終止分發(fā)剂跟。
Ps:這里的
onEvent
調(diào)用的其實(shí)就是上面dispatchEvent
代碼中的 lambda 表達(dá)式减途。如果想要?jiǎng)?chuàng)建一個(gè)觸摸事件的優(yōu)先級(jí)比當(dāng)前所有的觸摸事件優(yōu)先級(jí)都高話,只需要把 fixedPriority 的數(shù)值設(shè)為 < 0 即可曹洽。
當(dāng)正在分發(fā)事件的時(shí)候(_inDispath > 0)鳍置,添加和刪除監(jiān)聽器事件
EventDispatcher
中有表示當(dāng)前分發(fā)的事件數(shù)的私有成員變量 _inDispatch
,它是一個(gè) int
類型的數(shù)據(jù)送淆,用于表示當(dāng)前正有多少事件正在分發(fā)税产。既然是表示事件正在分發(fā)的數(shù)量,可定有 ++, -- 的操作偷崩,在DispatchEvent
中會(huì)有+1和-1的操作辟拷,但是藏得比較深,怎么個(gè)深法阐斜,看下述源碼衫冻。
void EventDispatcher::dispatchEvent(Event* event)
{
...
DispatchGuard guard(_inDispatch);
...
}
我去,這啥都都沒有摆顺觥隅俘??笤喳?發(fā)現(xiàn)這里面只有使用 _inDispatch
創(chuàng)建了一個(gè)變量为居,并沒有++,- -操作杀狡。其實(shí) DispatchGuard
這個(gè)類的構(gòu)造函數(shù)的參數(shù)是一個(gè) int
類型的引用蒙畴,構(gòu)造時(shí)候?qū)ζ?1,析構(gòu)函數(shù)的時(shí)候會(huì)-1操作呜象。用法很妙膳凝,借助了局部變量是在棧里面這個(gè)特性,當(dāng)方法DispatchEvent
結(jié)束的時(shí)候董朝,局部變量guard會(huì)析構(gòu)鸠项,此時(shí)會(huì)-1操作。下面是 DispatchGuard
的源碼子姜。
class DispatchGuard
{
public:
DispatchGuard(int& count):
_count(count)
{
++_count;
}
~DispatchGuard()
{
--_count;
}
private:
int& _count;
};
了解了_inDispatch
的意義,我們來看看添加事件監(jiān)聽器和刪除事件監(jiān)聽器的時(shí)候楼入,如果正在分發(fā)事件(_inDispatch > 0
)會(huì)怎么處理哥捕。
void EventDispatcher::addEventListener(EventListener* listener)
{
if (_inDispatch == 0)
{
forceAddEventListener(listener);
}
else
{
_toAddedListeners.push_back(listener);
}
listener->retain();
}
上述代碼是監(jiān)聽器添加的時(shí)候的代碼,可以看到如果當(dāng)時(shí)正在分發(fā)事件嘉熊,會(huì)把當(dāng)前需要添加的監(jiān)聽器添加到待添加向量(_toAddedListeners
)中遥赚,那么也就是說在事件分發(fā)完畢之后監(jiān)聽器需要從toAddedListeners
中轉(zhuǎn)移到正式向量中,這部分代碼可以在updateListeners
中看到阐肤,此方法會(huì)在事件分發(fā)結(jié)束之后調(diào)用凫佛。
void EventDispatcher::updateListeners(Event* event)
{
CCASSERT(_inDispatch > 0, "If program goes here, there should be event in dispatch.");
if (_inDispatch > 1)
return;
...
if (!_toAddedListeners.empty())
{
for (auto& listener : _toAddedListeners)
{
forceAddEventListener(listener);
}
_toAddedListeners.clear();
}
if (!_toRemovedListeners.empty())
{
cleanToRemovedListeners();
}
}
代碼中讲坎,除了添加事件監(jiān)聽器有個(gè)待加入向量外,刪除事件監(jiān)聽器也有一個(gè)待刪除向量(不過這個(gè)好像是廢話)愧薛,開頭有判斷當(dāng)前是否還是處于事件分發(fā)中晨炕。
Cocos Bug 之 DirtyFlag
上面的代碼中,我刪了一些代碼毫炉,因?yàn)榇a太多影響閱讀瓮栗。其中就有很多涉及到了Dirty Flag,望文取義的話是臟標(biāo)記瞄勾,問題是這個(gè)臟是什么臟费奸?
下面來看一下添加監(jiān)聽器和移除監(jiān)聽器的代碼部分。
// 添加監(jiān)聽器
void EventDispatcher::forceAddEventListener(EventListener* listener)
{
EventListenerVector* listeners = nullptr;
...
if (listener->getFixedPriority() == 0)
{
setDirty(listenerID, DirtyFlag::SCENE_GRAPH_PRIORITY);
...
}
else
{
setDirty(listenerID, DirtyFlag::FIXED_PRIORITY);
}
}
// 移除監(jiān)聽器
void EventDispatcher::removeEventListener(EventListener* listener)
{
...
for (auto iter = _listenerMap.begin(); iter != _listenerMap.end();)
{
...
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);
}
}
...
}
代碼中可以看到进陡,這個(gè) DirtyFlag 是設(shè)置給 ListenerID
的愿阐,每當(dāng)新添加一個(gè) Listener
,或則刪除一個(gè) Listener
的時(shí)候趾疚,就會(huì)給當(dāng)前 Listener
的 ListenerID
添加一個(gè) DirtyFlag换况。說明這個(gè)臟是指 ListenerID
對(duì)應(yīng)的監(jiān)聽器向量列表需要重新排序了,如果不臟就不需要排序盗蟆。
那么問題來了:
照理只要出現(xiàn)了刪除戈二,修改,添加監(jiān)聽器的時(shí)候喳资,監(jiān)聽器列表需要重新排序觉吭,都需要設(shè)置相應(yīng)的 DirtyFlag 操作。但是 Cocos-2dx v3.10 里面的 updateListeners
函數(shù)有刪除監(jiān)聽器的操作仆邓,然而并沒有設(shè)置相應(yīng)的 DirtyFlag 操作鲜滩。此問題我在 Cocos2dx github 的 issues中有回答問題鏈接
這個(gè)就是一個(gè) Bug 了,放著不管會(huì)拋出以下異常
CCASSERT(listeners->getGt0Index() <= static_cast<ssize_t>(fixedPriorityListeners->size()), "Out of range exception!");
代碼中的 Gt0Index()
方法其實(shí)就是獲取到當(dāng)前監(jiān)聽器里誒包中 fixedPriority == 0 的監(jiān)聽器在監(jiān)聽器向量中的位置节值,它只有在給 Listener
排序的時(shí)候會(huì)設(shè)置徙硅,但是如果更新了對(duì)應(yīng) ListenerID
的向量(EventListenerVector
),但是沒有重新排序搞疗,就會(huì)出現(xiàn) _gt0Index
未及時(shí)更新的情況嗓蘑,導(dǎo)致拋出這個(gè)異常。
排序的時(shí)候匿乃,會(huì)判斷排序的這個(gè) ListenerID
是否處于Dirty
的狀態(tài)桩皿,只有臟狀態(tài)才會(huì)排序,這算優(yōu)化吧幢炸,所以必須在 updateListener
的時(shí)候加上 DirtyFlag泄隔。
Bug 修復(fù)
void EventDispatcher::updateListeners(Event* event)
{
...
auto onUpdateListeners = [this](const EventListener::ListenerID& listenerID)
{
...
if (sceneGraphPriorityListeners)
{
for (auto iter = sceneGraphPriorityListeners->begin(); iter != sceneGraphPriorityListeners->end();)
{
auto l = *iter;
if (!l->isRegistered())
{
...
// if item in toRemove list, remove it from the list
setDirty(l->getListenerID(), DirtyFlag::SCENE_GRAPH_PRIORITY);
..
}
...
}
}
if (fixedPriorityListeners)
{
for (auto iter = fixedPriorityListeners->begin(); iter != fixedPriorityListeners->end();)
{
auto l = *iter;
if (!l->isRegistered())
{
...
// if item in toRemove list, remove it from the list
setDirty(l->getListenerID(), DirtyFlag::FIXED_PRIORITY);
...
}
...
}
}
...
};
...
}
PS: 場(chǎng)景切換的時(shí)候
EventDispatcher
會(huì)設(shè)置成_isEnabled = false;
這時(shí)候分發(fā)自定義事件是無效的。