事件管理器中游戏的迭代程序在注销时失效



我正在为学校制作的游戏编写EventManager类。它使用事件的双队列系统,并保存一个由事件类型和已注册ID向量组成的无序映射,以响应特定事件。我遇到的问题是,某些事件(如单击主菜单的播放按钮)可能会导致参与者取消注册事件。虽然这是有意的,但它会导致迭代器出现问题,而这并不是有意的。在从主菜单切换到游戏的示例中,它会破坏主菜单中的所有参与者(按钮等)。反过来,它们会用事件管理器注销自己,事件管理器会从存储在unrdered_map中的相应向量中删除它们的id,并使在循环开始时抛出异常的迭代器无效。因此,任何事件都不会导致参与者注销任何内容。这是不可取的,因为游戏中的一些触发对象可能是一次性触发,在这一点上,它们需要注销自己以接收更多事件。如有任何帮助或想法,我们将不胜感激。

    void EventManager::Flush()
    {
        Event* current;
        while (m_eventList[m_current].size() > 0)
        {
            current = m_eventList[m_current].front();
            m_eventList[m_current].pop_front();
            unordered_map<string, vector<IDTYPE>>::iterator it =  m_registeredEvents.find(current->GetType());
            if (it == m_registeredEvents.end())
                continue;
            vector<IDTYPE>* toProcessAct = &(it->second);
            vector<IDTYPE>::iterator actIt = toProcessAct->begin();
            while (actIt != toProcessAct->end())                   //this becomes invalid
            {
                Actor* temp = ACTORS->GetActor(*actIt);
                if (temp == NULL)
                    actIt = toProcessAct->erase(actIt);
                else
                {
                    actIt++;
                    temp->Process(current);              //Because this may unregister events
                }
            }
            delete current; current = 0;
        }
        Swap();
    }
void EventManager::UnregisterEvent(string Event, IDTYPE actor)
{
    unordered_map<string, vector<IDTYPE>>::iterator it = m_registeredEvents.find(Event);
    //Found
    if (it != m_registeredEvents.end())
    {
        //Find if actor is already registered
        vector<IDTYPE>::iterator actIt = it->second.begin();
        while (actIt != it->second.end())
        {
            if (actor == *actIt)
            {
                it->second.erase(actIt);
                return;
            }
            actIt++;
        }
    }
}

这是射中自己脚部的好方法

    Event* current;  // not the issue here but you should always initialize your variables.
    while (m_eventList[m_current].size() > 0) {
        current = m_eventList[m_current].front(); // current points the the first element
        m_eventList[m_current].pop_front(); // now the first element gets destroyed
        unordered_map<string, vector<IDTYPE>>::iterator it =  m_registeredEvents.find(current->GetType()); // its pure coincident if this works.

pop_front()破坏front元素,使指针无效,然后输入未定义的行为。制作一份记录的副本,这样你也不会有销毁它的问题。

此外,删除后元素的所有迭代器都是无效的,所以如果您的代码将迭代器保存为一种状态,您会受到伤害,那么为了安全起见,假设所有删除都会使该容器或相关容器的所有迭代器无效。

如果你正在运行一个多线程程序,那么其他线程可能会更改内容,那么至少要使用一个互斥锁来保护它

好的,现在是你真正想要的:

        while (actIt != toProcessAct->end()) {  // end() should update but actIt doesn't.
            Actor* temp = ACTORS->GetActor(*actIt);
            if (temp == NULL)
                actIt = toProcessAct->erase(actIt);
            else {
                actIt = temp->Process(actIt, current); // make Process return the new valid It
            }
        }
        delete current; // would fail as there is a continue higher up!!!
        current = 0; // if using C++11 use nullptr instead else NULL

如果在actIt之后没有有效的迭代器,则让Process返回新的有效迭代器或toProcessAct->end()。

一种解决方案可以是迭代事件处理程序向量的副本,而不是

vector<IDTYPE>* toProcessAct = &(it->second);

写入

vector<IDTYPE> toProcessAct = it->second;

(并对要编译的代码进行相应的更改)
此外,如果在Flush()开始时复制整个m_registeredEvents,可能会更快,因此不必多次复制向量。


另一种解决方案是推迟注销,即UnregisterEvent()将注销请求排入单独的队列,然后再执行,例如在Flush()结束时,而不是立即执行。

最新更新