How to handle JNI callback, hardware interrupt?

I’m facing with a lot of crash when try to addChild, removeChild inside hardware interrupts, or JAVA callback.

My game has a button to call to java, in order to use voice recognition.
The context:
C++: button record pressed -> JAVA: start voice recognition -> C++: return;
JAVA: has result -> C++: result handle function -> C++: addchild, removechild, etc…-> crashed randomly.

I figured out it is crashed because of I tried to change the game data when cocos is doing the samething, in the same area.

Ex: when cocos is rendering layerA, JAVA also tried to remove layerA -> crashed.

Does cocos have any solution for this context ?
May be a callback queue which will be processed in the next game loop ?

I think this make sense because the need to change the Drawing scene when you press some hard button: back key, volume key, etc… is very necessary

My sample code:

#define RUNNING_SCENE       (Director::getInstance()->getRunningScene())
// start recognition
void AppDelegate::startRecog(void)
{
    auto lRecording = CSLoader::createNode("LayerRecording.csb");
    RUNNING_SCENE->addChild(lRecording, Z_RECORDING, TAG_LAYER_RECORDING);
    JniMethodInfo t;
    if (JniHelper::getStaticMethodInfo(t,
            "org/cocos2dx/cpp/AppActivity",
            "startRecog",
            "()V"))
    {
        t.env->CallStaticVoidMethod(t.classID, t.methodID);
        t.env->DeleteLocalRef(t.classID);
    }
}
// JNI callback
JNIEXPORT void JNICALL Java_org_cocos2dx_cpp_AppActivity_recogEnded(JNIEnv* env, jobject thiz, jint errCode)
{
    auto lRecording = RUNNING_SCENE->getChildByTag(TAG_LAYER_RECORDING);
    if(lRecording)
    {
        RUNNING_SCENE->removeChildByTag(TAG_LAYER_RECORDING);
    }
}

Hello @quano1

Maybe Custom Event will help you. If your C++: result handle function will generate an event. http://www.cocos2d-x.org/docs/programmers-guide/8/index.html

Also it would be nice if you provide some code.

Thank you, Magniffect. Your solution seems very reasonable.
But it has a limitation, we have to add all of events for each scene.

Actually, I tested with scheduleOnce(...) hundred times but it has NOT crashed yet.
I’m not sure it absolutely avoid crashed or just become to more rarely.
I’ll double check how cocos schedule event via scheduleX function.

It would be nice if someone can share the knowledge of schedule event area .

I think your solution is better, it is more short.
The scheduleOnce() calls schedule(), and the schedule() is called between frames. So it must be safe.

I don’t understand why you need remove child immediately ? i think that is bad idea, addChild() and removeChild() is expensive method.
That’s why we call it in init() and release() method. I agree with Magniffect, you can make simple garbageCollector() for remove child, for example giving flag WILL_REMOVED and set visible to false to child that you want to be removed. Then use schedule function (e.g every 5 minutes) you will execute removeChild() to children that have flag WILL_REMOVED.
For schedule event, I think you can implement Observer design pattern and Obejct Pool design pattern. CMIIW

Schedule cannot help
The crash will occurs when cocos and JNI do stuff with schedule at the same time.
I tried and it crashed.
I beleived magniffect’s way is the best solution cocos has
@fikry I have to remove because to add all of popup events in each scene init is more expensive
You can imagine what’s happens if we have to handle more than 20 events popup?
Create all of them from init is waste memory
Sorry for my urgly english

Other solution, you can use game update for remove event.
I will use setVisible(false) instead removeChild() because if sometime I need to show popup again so I need not to call addChild().
Good Luck.

Sorry, my bad english too :smiley:

@quano1
To be honest, I am surprised that this crashed. Could you provide some code so that I can look into it later and improve my knowledge of the schedule.

@fikry

Interesting. How to do it? Any code? (Yes, I love the code)

Sorry for my bad english too.

@Magniffect: here you are:

void AppDelegate::startRecog(void)
{
    LOGI("");
    cocos2d::experimental::AudioEngine::pauseAll();
    RUNNING_SCENE->scheduleOnce(
        [=](float dt)
        {
            auto lRecording = CSLoader::createNode("LayerRecording.csb");
            (lRecording)->setAnchorPoint(Vec2(0.5f, 0.5f));
            (lRecording)->setScale(SCREEN_SCALE_Y(lRecording));
            (lRecording)->setPosition(SCREEN_X(0.5f), SCREEN_Y(0.5f));
            RUNNING_SCENE->addChild(lRecording, LAYER_MAX, LAYER_COLOR);
        }, 0.f, "startRecog");

#if (CC_TARGET_PLATFORM==CC_PLATFORM_ANDROID)
    JniMethodInfo t;
    if (JniHelper::getStaticMethodInfo(t,"org/cocos2dx/cpp/AppActivity","startRecog","()V"))
    {
        t.env->CallStaticVoidMethod(t.classID, t.methodID);
        t.env->DeleteLocalRef(t.classID);
    }
#endif
}

JNIEXPORT void JNICALL Java_org_cocos2dx_cpp_AppActivity_recogEnded(JNIEnv* env, jobject thiz, jint errCode)
{
    LOGI("%d", errCode);
    cocos2d::experimental::AudioEngine::resumeAll();

    if(AppDelegate::sGotResult == false)
    {
        AppDelegate::sFailedCount++;
    }

    RUNNING_SCENE->scheduleOnce(
    [=](float dt)
    {
        auto lRecording = RUNNING_SCENE->getChildByTag(LAYER_COLOR);
        if(lRecording)
        {
            RUNNING_SCENE->removeChildByTag(LAYER_COLOR);
        }
    }
    , 0.f, "recogEnded");
}

It become more rarely to crash. But it’s still crashed.
It’s understandable. At the time cocos’s doing stuff with schedule, suddently JNI tries to insert a new event, and ofcourse, crash occurs.

your solution is the same way cocos handles touching event, hard button. This means it should not be crashed.

Because touching and hard button is hardware interrupt. It’s the same with my context.

:wink: Im wrong. dispatchEvent will triggers event immediately.

anyway, thank all of you :blush:

p/s: all of bad english talking together, do not need to say sorry :smile:

yeah, finally I have solved this sh*t.

JNI callback run in seperated thread, it means when JNI occurs a callback, it will not block cocos main thread.

I was afraid that JNI callback will blocks cocos main thread. I should check this first :expressionless:

Ok! To solve this, just use std::mutex and scheduleOnce.

like this:

void MyGame::update(float dt)
{
    jniMutex.unlock();
    // do something
    jniMutex.lock();
}

JNI callback()
{
    jniMutex.lock();
    // scheduleOnce something
    jniMutex.unlock();
}

I want to make sure JNI callback run inside MyGame::update(float), therefore I call unlock() from the begining, and call lock() at the end.

Beside that, we should use scheduleOnce instead of try to modify Node structure directly inside update function.

I am happy to know that you have solved this problem!

p.s. I have not thanked you for code. Thank you!
And thank you for sharing the solution. This can be very helpful for many.

Nice solution, thank you for sharing the solution. :smiley: