爆裂吧现实

不想死就早点睡

0%

事件分发机制源码解析(二)

此文章紧接事件分发机制源码解析(一),建议顺序翻阅。

当正在分发事件的时候(_inDispath > 0),添加和删除监听器事件

EventDispatcher 中有表示当前分发的事件数的私有成员变量 _inDispatch ,它是一个 int 类型的数据,用于表示当前正有多少事件正在分发。既然是表示事件正在分发的数量,可定有 ++, –  的操作,在DispatchEvent中会有+1和-1的操作,但是藏得比较深,怎么个深法,看下述源码。  

1
2
3
4
5
6
7
8
void EventDispatcher::dispatchEvent(Event* event)
{
    ...
 
    DispatchGuard guard(_inDispatch);
 
    ...
}

 
我去,这啥都都没有啊???发现这里面只有使用 _inDispatch 创建了一个变量,并没有++,- -操作。其实 DispatchGuard 这个类的构造函数的参数是一个 int 类型的引用,构造时候对其+1,析构函数的时候会-1操作。用法很妙,借助了局部变量是在栈里面这个特性,当方法DispatchEvent结束的时候,局部变量guard会析构,此时会-1操作。下面是 DispatchGuard 的源码。  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class DispatchGuard
{
public:
    DispatchGuard(int& count):
            _count(count)
    {
        ++_count;
    }
 
    ~DispatchGuard()
    {
        --_count;
    }
 
private:
    int& _count;
};

了解了_inDispatch的意义,我们来看看添加事件监听器和删除事件监听器的时候,如果正在分发事件(_inDispatch > 0)会怎么处理。  

1
2
3
4
5
6
7
8
9
10
11
12
13
void EventDispatcher::addEventListener(EventListener* listener)
{
    if (_inDispatch == 0)
    {
        forceAddEventListener(listener);
    }
    else
    {
        _toAddedListeners.push_back(listener);
    }
 
    listener->retain();
}

 
上述代码是监听器添加的时候的代码,可以看到如果当时正在分发事件,会把当前需要添加的监听器添加到待添加向量(_toAddedListeners)中,那么也就是说在事件分发完毕之后监听器需要从toAddedListeners中转移到正式向量中,这部分代码可以在updateListeners中看到,此方法会在事件分发结束之后调用。  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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();
    }
}

 
代码中,除了添加事件监听器有个待加入向量外,删除事件监听器也有一个待删除向量(不过这个好像是废话),开头有判断当前是否还是处于事件分发中。  

Cocos Bug 之 DirtyFlag

上面的代码中,我删了一些代码,因为代码太多影响阅读。其中就有很多涉及到了Dirty Flag,望文取义的话是脏标记,问题是这个脏是什么脏?

下面来看一下添加监听器和移除监听器的代码部分。  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 添加监听器
void EventDispatcher::forceAddEventListener(EventListener* listener)
{
    EventListenerVector* listeners = nullptr;
    ...
 
    if (listener->getFixedPriority() == 0)
    {
        setDirty(listenerID, DirtyFlag::SCENE_GRAPH_PRIORITY);
 
        ...
    }
    else
    {
        setDirty(listenerID, DirtyFlag::FIXED_PRIORITY);
    }
}
 // 移除监听器
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);
            }
        }
 
    ...
}

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

那么问题来了:
照理只要出现了删除,修改,添加监听器的时候,监听器列表需要重新排序,都需要设置相应的 DirtyFlag 操作。但是 Cocos-2dx v3.10 里面的 updateListeners 函数有删除监听器的操作,然而并没有设置相应的 DirtyFlag 操作。此问题我在 Cocos2dx github 的 issues中有回答问题链接

这个就是一个 Bug 了,放着不管会抛出以下异常  

1
CCASSERT(listeners->getGt0Index() <= static_cast<ssize_t>(fixedPriorityListeners->size()), "Out of range exception!");

 
代码中的 Gt0Index() 方法其实就是获取到当前监听器里诶包中 fixedPriority == 0 的监听器在监听器向量中的位置,它只有在给 Listener 排序的时候会设置,但是如果更新了对应 ListenerID 的向量(EventListenerVector),但是没有重新排序,就会出现 _gt0Index 未及时更新的情况,导致抛出这个异常。
排序的时候,会判断排序的这个 ListenerID 是否处于Dirty的状态,只有脏状态才会排序,这算优化吧,所以必须在 updateListener 的时候加上 DirtyFlag
 
Bug 修复

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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: 场景切换的时候 EventDispatcher 会设置成 _isEnabled = false; 这时候分发自定义事件是无效的。