My problem was calling the callback on other then CocosGUI thread.
This is my complete solution. Maybe someone comes with better solution not involving the instance variables this->_functionName...
and thus allowing multiple calls at the same time!
In you code just remove all the SDEBUG
calls.
.h
protected:
bool isJSCallInProgress;
string _functionName;
bool _isOK;
string _errorMsg;
.cpp
////////////////////////////////////////////////////////////////////////////////////////////
//
//
void AssetManager_js::_callJavaScriptCallback (const std::string& functionName, bool isOK, const std::string& errorMsg)
{
SDEBUG(LEVEL_DEBUG, "assets-cpp", "CALLED: AssetManager_js::_callJavaScriptCallback(functionName=%s; bool isOK=%s; errorMsg=%s;)",
functionName.c_str(), BOOLStr(isOK), errorMsg.c_str());
// <C> C++ to JS calls must be made on CocosUI thread otherwise the variables are not properly set/accessed in JS (you get null for assigned var in another thread)
// Thus this method is called on CocosUI thread!
//
// <REF> https://github.com/cocos2d/cocos2d-x/blob/fb55c2cd63ddfec911ef3ffa468edd048b865957/extensions/assets-manager/AssetsManager.cpp
// <REF> https://github.com/cocos2d/cocos2d-x/pull/4401
// <REF> http://discuss.cocos2d-x.org/t/proposal-for-v3-0-thread-helper-utilities/8818/11
//
// <M><C> performFunctionInCocosThread() is called on another thread and needs call-life-time variables.
// Such variables are shared thus only 1 JS call can be made at the same time!
//
if (this->isJSCallInProgress == true) {
SDEBUG(LEVEL_DEBUG, "assets-cpp", "QUIT: AssetManager_js::_callJavaScriptCallback(): another JS call is registered. We can serve only 1 at the time. QUITing!", "");
return;
}
this->isJSCallInProgress = true;
this->_functionName = functionName;
this->_isOK = isOK;
this->_errorMsg = errorMsg;
Director::getInstance()->getScheduler()->performFunctionInCocosThread([&, this]{
this->__callJavaScriptCallback(this->_functionName, this->_isOK, this->_errorMsg);
});
SDEBUG(LEVEL_DEBUG, "assets-cpp", "EXIT: AssetManager_js::_callJavaScriptCallback()", "");
}
////////////////////////////////////////////////////////////////////////////////////////////
//
//
void AssetManager_js::__callJavaScriptCallback (const std::string& functionName, bool isOK, const std::string& errorMsg)
{
SDEBUG(LEVEL_DEBUG, "assets-cpp", "CALLED: AssetManager_js::__callJavaScriptCallback(functionName=%s; bool isOK=%s; errorMsg=%s;)",
functionName.c_str(), BOOLStr(isOK), errorMsg.c_str());
// <M><REF> http://discuss.cocos2d-x.org/t/calling-javascript-from-c-crashes-on-android/22805
//
// ScriptingCore* sc = ScriptingCore::getInstance();
// jsval value = std_string_to_jsval(ScriptingCore::getInstance()->getGlobalContext(), errorMsg.c_str());
// ScriptingCore::getInstance()->executeFunctionWithOwner(OBJECT_TO_JSVAL(sc->getGlobalObject()),"functionTestCallFromAndroid", 1, &value);
//
// JS call: onPurchaseForProductResponse
//
//
js_proxy_t* p = jsb_get_native_proxy(this);
SDEBUG(LEVEL_DEBUG, "assets-cpp", "AssetManager_js::__callJavaScriptCallback.jsb_get_native_proxy()", "");
// is the JS callback function registered?
if (p == nullptr) {
// <C> this function may be invoked either from JS BuyScene->onBuy() or from queued processing of pending transactions (no BuyScene thus no JS callback);
// p is NULL if not called from JS;
// <M> I still might want to let JS code know about this finished transtion. Then global JS function may be invoked (withdout need of p)
// See <REF> http://discuss.cocos2d-x.org/t/js-callfunctionname-crashed-on-iap-finished-callback/17254
//
SDEBUG(LEVEL_DEBUG, "assets-cpp", "QUIT: AssetManager_js::__callJavaScriptCallback(): Event %s received but NO JS CALLBACK IS REGISTERED. QUITing. isOK=%s; errorMsg=%s;",
functionName.c_str(), BOOLStr(isOK), errorMsg.c_str());
this->isJSCallInProgress = false;
return;
}
JSContext* cx = ScriptingCore::getInstance()->getGlobalContext();
SDEBUG(LEVEL_DEBUG, "assets-cpp", "AssetManager_js::__callJavaScriptCallback.getGlobalContext()", "");
jsval retval;
jsval v[] = {
v[0] = BOOLEAN_TO_JSVAL(isOK),
// <C> this line gives "EXC_BAD_ACCES". Fixed by:
// <REF> http://discuss.cocos2d-x.org/t/js-callfunctionname-crashed-on-iap-finished-callback/17254
//v[1] = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, errorMsg.c_str()))
v[1] = std_string_to_jsval(cx, errorMsg)
};
// <REF> https://github.com/cocos2d/cocos2d-x/blob/fb55c2cd63ddfec911ef3ffa468edd048b865957/extensions/assets-manager/AssetsManager.cpp
// <REF> https://github.com/cocos2d/cocos2d-x/pull/4401
// <REF> http://discuss.cocos2d-x.org/t/proposal-for-v3-0-thread-helper-utilities/8818/11
//static std::string _functionName = functionName;
//SDEBUG(LEVEL_DEBUG, "assets-cpp", "******* BEFORE A CALL OF XXX(%s)", functionName.c_str());
// <FIX><UPGRADE>
// cocos-3.3: bool executeFunctionWithOwner(jsval owner, const char *name, uint32_t argc = 0, jsval* vp = NULL, jsval* retVal = NULL);
// cocos-3.5: bool executeFunctionWithOwner(jsval owner, const char *name, uint32_t argc, jsval *vp, JS::MutableHandleValue retVal);
//
// 3.3: ScriptingCore::getInstance()->executeFunctionWithOwner(OBJECT_TO_JSVAL(p->obj), functionName.c_str(), 2, v, &retval);
//
SDEBUG(LEVEL_DEBUG, "assets-cpp", "******* CALLING executeFunctionWithOwner(IS_NULL(p->obj):%s; functionName=%s; IS_NULL(v):%s;)",
p->obj != NULL ? "NO" : "YES", functionName.c_str(), v != NULL ? "NO" : "YES");
ScriptingCore::getInstance()->executeFunctionWithOwner(OBJECT_TO_JSVAL(p->obj), functionName.c_str(), 2, v); // <C> I don't use the retVal anyway
// });
this->isJSCallInProgress = false;
SDEBUG(LEVEL_DEBUG, "assets-cpp", "EXIT: AssetManager_js::__callJavaScriptCallback()", "");
}