[Solved] Some EventCustom get lost / aren't dispatched or listened

EDIT: Solved. It wasn’t anything about Cocos2d-x / events getting lost, but fault of a bug that I introduced in my own codebase. Sorry about that :sweat_smile: And thank you anyway, great Cocos2d-x community! :smiley:


Hello,

I have a problem with Cocos2d-x (3.9) custom events (cocos2d::EventCustom and cocos2d::EventListenerCustom). In the game I am currently working on, I’ve just implemented a missions feature: Get 50 gems, defeat 10 enemies, etc. So I thought the best approach to this was to send custom events each time the player collects a gem or defeats an enemy, and a Mission class will listen to all these custom events and keep a counter to determine when the mission is accomplished.

For example, let’s say the mission is “get 20 gems” (I’ll adapt the code to be more readable and with hardcoded values instead of generic so it is more readable). First, I need to register a EventListenerCustom of type "gemCollected":

// Somewhere in Missions.cpp file

// Register a EventListenerCustom to track gemCollected events
_gemCollectedListener = cocos2d::EventListenerCustom::create("gemCollected", [this](cocos2d::EventCustom* event) {

    _collectedGems++;
    CCLOG("gemCollected listener callback n. %d", _collectedGems);

    // Determine if the mission has been completed
    if (_collectedGems >= 20) 
        CCLOG("Mission complete!");
});

cocos2d::Director::getInstance()->getEventDispatcher()->addEventListenerWithFixedPriority(_gemCollectedListener, 1);

Then I dispatch a "gemCollected" custom event each time the player collects a gem:

// Somewhere in the game, where the collision detection between the gem and the player is checked
cocos2d::Director::getInstance()->getEventDispatcher()->dispatchCustomEvent("gemCollected");

The problem is that if I play a game in which I collect 20 gems (the mission requires 20 to be completed), the callback above should have been called each time I collected a gem and have an output like this:

gemCollected listener callback n. 1
gemCollected listener callback n. 2
gemCollected listener callback n. 3
...
gemCollected listener callback n. 20
Mission complete!

But instead the callback is called, say, 17 times. Or 15. Or 19. And that feels weird because when the Game Over screen appears you can see the amount of gems you got, and if you have a mission about collecting gems it also tells you how many gems you collected out of a total to complete the mission, and there you clearly see that both values doesn’t match.

It’s like some events are getting lost, or not being dispatched / listened. And I can’t see a pattern. I also tried to dive into the event dispatcher code, but I don’t know where to look at.

Any clue? Thank you in advance!

I’m using events for a lot of different stuffs across the game, and never seen this problem. I’ve just noticed that you are registering a bit differently then i do, so that might be the problem. Maybe something is swallowing your event.

#define REGISTER(name, selector) cocos2d::Director::getInstance()->getEventDispatcher()->addCustomEventListener(name, CC_CALLBACK_1(selector, this))

#define UNREGISTER_LISTENER(listener) cocos2d::Director::getInstance()->getEventDispatcher()->removeEventListener(listener)

These are the macros i’m using to register and unregister custom event listeners

1 Like

I tried to do the same way as you but still loosing events. I’ve just played a game in which I collected 95 gems but only 60 have been registered (the mission callback has been called 60 times only).

I also thought about something swallowing some events, but I don’t know what or why it would swallow only some of them…

Anyway, thank you for your response. Those macros are quite useful :slight_smile:

I hope someone can help me, because this seems to be one of those bugs in which I get stuck for weeks… and this project’s due date is getting closer and closer :sweat:

Is your collision code working properly?
Log when the event will be dispatched and compare that to the number of callbacks executed.

You can also set user data for the event and get it back in the callback.
This is an easy method to track a lost event.

Is this the number you log, when the event was dispatched?

If you don’t collect all gems, of course those value will differ.

Are you saying that the numbers differ, even if you collected all gems?

Adding a custom event listener ist just a fixed priority event listener with a priority of 1.

About the numbers on the Game Over screen, I was referring to numbers on screen: Typical “your score” numbers, and collected gems is amongst them. Something like:

Your score: 5000
Collected gems: 95

Mission: Collect 100 gems: 60/100

[Share button]  [Retry button]

The Collected gems: 95 label shows the value of a variable that holds the total collected gems on that game.

The Mission: Collect 100 gems: 60/100 label shows info of the Missions class, which has another variable to count the amount of listened mission events (it can be anything, but in this particular case is collected gems).

They are separate variables on different classes. The first one rises by 1 at the collision check when it is true, and the "gemCollected" event is dispatched at this moment too. The second one rises by 1 when the event callback is invoked. I don’t get why the values are that different (95 and 60) unless some events are being discarded or swallowed in the way. I’ll try to set the user data field and check if some of them are getting lost, but in that case I wouldn’t know what to do either :confounded:

EDIT: Solved!
I found the cause! I was reviewing the part of the code where I check the collision between the player and gems, and it seems that I put the event dispatching code in the wrong place. As I was having some sound problems on Android devices when I collected several gems at once, I set a small time check in order to avoid playing too many sounds in a short time. Well… the problem is that I dispatched the event in there and I didn’t noticed that I wrote that line of code in the wrong place.

Anyway, thank you very much @milos1290 and @iQD for taking time to help me! The Cocos2d-x community is great :smile:

Thanks, now it’s clear to me.

I thought, that you are displaying the gems put on the board available to collect.

Try to set a counter and also a time-stamp. Maybe you can see some pattern of a timeout or some time discrepancy.

You have to find out first, if they get lost or just time out. Additionally you can see, if the lost IDs are always different or the same every time.

It’s already solved! See my previous message edit. But thank you anyway for your advices! Maybe someday I’ll have the opportunity to put them in practice =)

I’ll edit the main post and title to mark it as solved.

It’s solved but you don’t explain how?

I explained it on the main post:

[quote=“iPruch, post:1, topic:25565”]
I found the cause! I was reviewing the part of the code where I check the collision between the player and gems, and it seems that I put the event dispatching code in the wrong place. As I was having some sound problems on Android devices when I collected several gems at once, I set a small time check in order to avoid playing too many sounds in a short time. Well… the problem is that I dispatched the event in there and I didn’t noticed that I wrote that line of code in the wrong place.[/quote]

Nothing to do with Cocos2d-x, but a huge mistake of mine :sweat_smile:

What about mine? :smile:

EventListenerCustom* _listeneronKelimelerReceived = EventListenerCustom::create("onKelimelerReceived", [=](EventCustom* event){
        this->schedule(schedule_selector(GameScene::onPlayTime), 1.0f);
        setGame();
        log("eventlerimiz onKelimelerReceived event calisti");
    });

    _eventDispatcher->addEventListenerWithSceneGraphPriority(_listeneronKelimelerReceived, this);

    EventListenerCustom* _listeneronWaitTimeReceived = EventListenerCustom::create("onWaitTimeReceived", [=](EventCustom* event){
        if(this->waitLock == false)
        {
            log("eventlerimiz onWaitTimeReceived event calisti false");

            this->waitLock = true;
            this->playLock = false;
            this->waitPlayLock = true;

            this->lblWord->setString("Kelimeler Bekleniyor...");

            this->schedule(schedule_selector(GameScene::onWaitTime), 1.0f);
        }

        log("eventlerimiz onWaitTimeReceived event calisti");
    });

    _eventDispatcher->addEventListenerWithSceneGraphPriority(_listeneronWaitTimeReceived, this);

    EventListenerCustom* _listeneronPlayTimeReceived = EventListenerCustom::create("onPlayTimeReceived", [=](EventCustom* event){
        if(this->playLock == false)
        {
            log("eventlerimiz onPlayTimeReceived event calisti false");

            this->waitLock = true;
            this->playLock = true;
            this->waitPlayLock = true;
        }

        log("eventlerimiz onPlayTimeReceived event calisti");
    });

    _eventDispatcher->addEventListenerWithSceneGraphPriority(_listeneronPlayTimeReceived, this);

    EventListenerCustom* _listeneronWaitPlayTimeReceived = EventListenerCustom::create("onWaitPlayTimeReceived", [=](EventCustom* event){
        if(this->waitPlayLock == false)
        {
            log("eventlerimiz onWaitPlayTimeReceived event calisti false");

            this->schedule(schedule_selector(GameScene::onWaitPlayTime), 1.0f);

            this->waitLock = true;
            this->playLock = true;
            this->waitPlayLock = true;
        }

        log("eventlerimiz onWaitPlayTimeReceived event calisti");
    });

    _eventDispatcher->addEventListenerWithSceneGraphPriority(_listeneronWaitPlayTimeReceived, this); 

if(d.HasMember("kelimeler") == true)
    {
        this->arrWords.clear();
        log("kalifikasyon testleri: kelimeler geldi");
        const rapidjson::Value& a = d["kelimeler"];

        for (rapidjson::SizeType i = 0; i < a.Size(); i++)
        {
            const rapidjson::Value& c = a[i];
            //log("kelime: %s", c["kelime"].GetString());

            const rapidjson::Value& kelimeHarfler = c["kelime"];
            for (rapidjson::SizeType t = 0; t < kelimeHarfler.Size(); t++)
            {
                this->buffVec.push_back(kelimeHarfler[t].GetString());
                log("test13 harf 2: %s", this->buffVec[t].c_str());
            }
            this->arrWords.push_back(this->buffVec);
            this->buffVec.clear();

            const rapidjson::Value& harfler = c["harfler"];
            for (rapidjson::SizeType t = 0; t < harfler.Size(); t++)
            {
                log("%s ", harfler[t].GetString());
                this->arr[i][t] = harfler[t].GetString();
            }
            //this->oyunStarted = true;
        }
        EventCustom eventKelimeler("onKelimelerReceived");

        _eventDispatcher->dispatchEvent(&eventKelimeler);
    }
    if(d.HasMember("waitTime") == true)
    {
        log("kalifikasyon testleri: waitTime geldi");
        log("timer saniye: %d", d["waitTime"].GetInt());

        this->waitSec = d["waitTime"].GetInt();

        EventCustom eventWaitTime("onWaitTimeReceived");

        _eventDispatcher->dispatchEvent(&eventWaitTime);
    }
    if(d.HasMember("playTime") == true)
    {
        log("kalifikasyon testleri: playTime geldi");

        this->playSec = d["playTime"].GetInt();

        EventCustom eventPlayTime("onPlayTimeReceived");

        _eventDispatcher->dispatchEvent(&eventPlayTime);
    }
    if(d.HasMember("waitPlayTime") == true)
    {
        log("kalifikasyon testleri: waitPlayTime geldi");

        this->waitPlaySec = d["waitPlayTime"].GetInt();

        EventCustom eventWaitPlayTime("onWaitPlayTimeReceived");

        _eventDispatcher->dispatchEvent(&eventWaitPlayTime);
    }

eventWaitTime & eventWaitPlayTime are not dispatched somehow while the other are. And log in if cases are printed. Anyone could figure out why?

I don’t see any difference between how you implemented each event listener, so if some are working I don’t see why those two aren’t… unless there’s something wrong with the dispatching. I didn’t understand this part:

Do you mean that you are displaying log messages and checking that the execution reaches that part of the code, but the event isn’t dispatched?

Yes. Look at the code:

if(d.HasMember("waitTime") == true)
    {
        log("kalifikasyon testleri: waitTime geldi");
        log("timer saniye: %d", d["waitTime"].GetInt());

        this->waitSec = d["waitTime"].GetInt();

        EventCustom eventWaitTime("onWaitTimeReceived");

        _eventDispatcher->dispatchEvent(&eventWaitTime);
    }

Here d.HasMember("waitTime") is true and so log("kalifikasyon testleri: waitTime geldi"); log("timer saniye: %d", d["waitTime"].GetInt()); logs are printed but eventWaitTime is not dispatched.

I don’t know if this has anything to do with your problem, but maybe you could try adding event listeners with addEventListenerWithFixedPriority instead of sceneGraphPriority…?

I was also looking inside CCEventDispatcher.cpp class, and maybe you could try also to set a breakpoint here to see what’s happening:

void EventDispatcher::addEventListenerWithSceneGraphPriority (EventListener* listener, Node* node)
{
    CCASSERT(listener && node, "Invalid parameters.");
    CCASSERT(!listener->isRegistered(), "The listener has been registered.");
    
    if (!listener->checkAvailable())
        return; <- BREAKPOINT HERE TO CHECK THIS
    
    listener->setAssociatedNode(node);
    listener->setFixedPriority(0);
    listener->setRegistered(true);
    
    addEventListener(listener);
}

Maybe the first event listener is added normally, and then the other two fail in this if (!listener->checkAvailable()) and nothing is done, causing your event listeners not to be added.

I’m talking without really knowing how event listener / dispatcher classes really work internally, but there’s no harm trying these two suggestions.

Nope.Tried addEventListenerWithFixedPriority but same :frowning:

Maybe because waitLock is true? I don’t see, where you are (re)setting it to false.

Does it log this?

No, unfortunately it doesn’t log :frowning:

Which version are you using?
I tested your code and it works without any issues on 3.7.

Test the events by calling them directly after each other in some function:

EventCustom eventWaitTime("onWaitTimeReceived");
_eventDispatcher->dispatchEvent(&eventWaitTime);

EventCustom eventPlayTime("onPlayTimeReceived");
_eventDispatcher->dispatchEvent(&eventPlayTime);

EventCustom eventWaitPlayTime("onWaitPlayTimeReceived");
_eventDispatcher->dispatchEvent(&eventWaitPlayTime);

The following output is created:

eventlerimiz onWaitTimeReceived event calisti
eventlerimiz onPlayTimeReceived event calisti
eventlerimiz onWaitPlayTimeReceived event calisti

I use 3.9. I will try it and post the result soon. Thanks.
Edit: I tried to dispatch onWaitTimeReceived and it is dispatched. But strangely, it doesn’t as it is in my sample code. Real confusion :S

Here is a very strange solution:

Changed EventListenerCustom* _listeneronWaitTimeReceived into EventListenerCustom* _listeneronWTimeReceived and onWaitTimeReceived to onWTimeReceived and it works…

EventListenerCustom* _listeneronWTimeReceived = EventListenerCustom::create("onWTimeReceived", [=](EventCustom* event){
        if(this->waitLock == false)
        {
            log("eventlerimiz onWaitTimeReceived event calisti false");

            this->waitLock = true;
            this->playLock = false;
            this->waitPlayLock = true;

            this->lblWord->setString("Kelimeler Bekleniyor...");

            this->schedule(schedule_selector(GameScene::onWaitTime), 1.0f);
        }

        log("eventlerimiz onWaitTimeReceived event calisti");
    });

Event stranger: If I change the name of the listenerWaitTimeReceived and compile, it is dispatched only for the first time running the game. Then it is not dispatched. But if i change the name of the listener and compile, it is dispatched again; but only for the first time as previous one.