Android and EasyNDK

I’m using cocos2d-x 3.10 and EasyNDKHelper https://github.com/alfonsocejudo/EasyNDK-for-cocos2dx3 My game is using microphone to read volume of speech and use that value to alter CCSprite SpriteFrame mouth of the character.
I’m testing sprite update condition every 0.08 second and if condition is met i’m sending message to cocos like this:

            Cocos2dxGLSurfaceView.getInstance().queueEvent(new Runnable() {
                @Override
                public void run() {
                    int delay = 80; //milliseconds
                    audioCallbackHandler.postDelayed(lipsyncRunnable, delay);
                }
            });

and lipsyncRunnable looks like this:

**        lipsyncRunnable = new Runnable() {
            @Override
            public void run() {
                    float peak = getLipsyncValue();
                            JSONObject message = new JSONObject();
                            try {
                                message.put("type","lipsync");
                                message.put("status",true);
                                message.put("peak",peak);

                            } catch (JSONException e) {
                                e.printStackTrace();
                            }
                            AndroidNDKHelper.SendMessageWithParameters("newLipsyncValue", message);
                            audioCallbackHandler.postDelayed(lipsyncRunnable, 80);
                }
                }
            }
        };

Everything works but occasionally(quite often) i get runtime errors:

06-06 14:44:36.491 7711-7758/org.cocos2dx.fun2oonx A/libc: Fatal signal 11 (SIGSEGV), code 1, fault addr 0x118 in tid 7758 (GLThread 19612)

with ndk-stack debug info:

tack frame #00 pc 00cca880  /data/app/org.cocos2dx.fun2oonx-2/lib/arm/libcocos2dcpp.so (cocos2d::Sequence::update(float)+956)
Stack frame #01 pc 00cc99e0  /data/app/org.cocos2dx.fun2oonx-2/lib/arm/libcocos2dcpp.so (cocos2d::ActionInterval::step(float)+584)
Stack frame #02 pc 00f65d88  /data/app/org.cocos2dx.fun2oonx-2/lib/arm/libcocos2dcpp.so (cocos2d::ActionManager::update(float)+244)
Stack frame #03 pc 00e0ce04  /data/app/org.cocos2dx.fun2oonx-2/lib/arm/libcocos2dcpp.so (void cocos2d::Scheduler::scheduleUpdate<cocos2d::ActionManager>(cocos2d::ActionManager*, int, bool)::{lambda(float)#1}::operator()(float) const+36)
Stack frame #04 pc 00e0eb78  /data/app/org.cocos2dx.fun2oonx-2/lib/arm/libcocos2dcpp.so (std::_Function_handler<void (float), void cocos2d::Scheduler::scheduleUpdate<cocos2d::ActionManager>(cocos2d::ActionManager*, int, bool)::{lambda(float)#1}>::_M_invoke(std::_Any_data const&, float)+60)
Stack frame #05 pc 00cd5f68  /data/app/org.cocos2dx.fun2oonx-2/lib/arm/libcocos2dcpp.so (std::function<void (float)>::operator()(float) const+88)
Stack frame #06 pc 00e483a8  /data/app/org.cocos2dx.fun2oonx-2/lib/arm/libcocos2dcpp.so (cocos2d::Scheduler::update(float)+196)
Stack frame #07 pc 00e08494  /data/app/org.cocos2dx.fun2oonx-2/lib/arm/libcocos2dcpp.so (cocos2d::Director::drawScene()+160)
Stack frame #08 pc 00e0cb3c  /data/app/org.cocos2dx.fun2oonx-2/lib/arm/libcocos2dcpp.so (cocos2d::DisplayLinkDirector::mainLoop()+136)
Stack frame #09 pc 006fcd6c  /data/app/org.cocos2dx.fun2oonx-2/lib/arm/libcocos2dcpp.so (Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeRender+40)
Stack frame #10 pc 008363f1  /data/dalvik-cache/arm/data@app@org.cocos2dx.fun2oonx-2@base.apk@classes.dex

I also tried to use this.runOnGLThread instead queueEvent but with no luck.

What could be wrong here? My guess is that the CCSprite has not yet updated CCSpriteFrame and the software wants to update it again which is causing some kind of race condition. Is there a way to prevent it?

I believe this crash is occurring because the Node you pass to NDKHelper::addSelector() has been destroyed.

NDKCallbackNode should really retain it’s Node reference, and there should be a method for removing selectors from NDKHelper by name and by Node.

So you could try adding a retain() call in NDKCallbackNode’s constructor (and release in destructor), add a method to NDKHelper to remove your selector and do so before it destructs, or don’t let your node destruct :stuck_out_tongue:

@almax27 I have tested your solution it doesn’t work.

All selectors are added in init method of Scene class. Scene class is added from CSB file.

NDKCallbackNode constructor and destructor pair is being called once when the scene is loaded when NDKHelper:addSelector() is being called so retaining the Node reference is only temporary.
However I have modified your proposal and added Node retain to NDKCallbackNode constructor and added release to removeSelectorsInGroup method in NDKHelper. And it’s still doesn’t work.

What do you mean by saying don’t let your node destruct? I thought if Scene is added from CSB file Cocos reference counting is doing the job and keeping Node’s in scene references.

I didn’t want to assume what node you might be passing into NDKHelper:addSelector() and what other logic you might have that could destroy said node. If it is a node destruction issue, just retaining the node before you pass it to addSelector() and never releasing would fix it (although cause a memory leak) :stuck_out_tongue:

Have you tried not providing a node at all?

NDKHelper:addSelector("mygroup", "myname", func, nullptr);

If no node is provided your function will be called immediately. Which depending on where the message comes from in Java may be on a different thread. So unless you want this behaviour you might need to call Scheduler::performFunctionInCocosThread() in your selector method or modify addSelector() to call your function on the cocos thread when node is null.

This is very insightful thanks a lot. I’m still working on it - so I should use runOnGlThread and performFunctionInCocosThread to ensure thread-safety of Android JNI calls?

Indeed, either method would be fine, but be aware this will defer the call. Sometimes this behaviour is undesired. For example if the app is in the background the cocos2d main loop is typically paused and/or the thread suspended by the OS, so if you receive a callback in Java for something like location services, bluetooth or a webrequest on another thread and call performFunctionInCocosThread(), the function will not execute until you come back to the foreground.

To avoid this problem, and allow callbacks off the main thread to be handled safely in all cases, you need to carefully protect data access using methods such as mutexs in c++ and the synchronized keyword in Java (thread safety is a pretty big field, but one worth reading up on).