上一篇我們講了按鍵回調,這一次我們來說說各種邏輯上的回調函數。
Cocos2d-x里面一共有三大類回調函數,第一是按鍵回調CCMenu相關的,第二類是定時器相關的回調
Schedule,第三類是Action相關的回調CallFunc。這些回調從最初的引擎版本中就存在著,一直到現在。
一、綁定代碼
在JSB的解決方案中,對于后兩類函數,引擎統一封裝成JSCallbackWrapper及其子類。
class JSCallbackWrapper: public cocos2d::Object { public: JSCallbackWrapper(); virtual ~JSCallbackWrapper(); void setJSCallbackFunc(jsval obj); void setJSCallbackThis(jsval thisObj); void setJSExtraData(jsval data); const jsval& getJSCallbackFunc() const; const jsval& getJSCallbackThis() const; const jsval& getJSExtraData() const; protected: jsval _jsCallback; jsval _jsThisObj; jsval _extraData; };
JSCallbackWrapper從名字就可以知道,是JS回調函數的包裝器。三個接口也一目了然,回調函數,this,外部數據。
// cc.CallFunc.create( func, this, [data]) // cc.CallFunc.create( func ) static JSBool js_callFunc(JSContext *cx, uint32_t argc, jsval *vp) { if (argc >= 1 && argc <= 3) { jsval *argv = JS_ARGV(cx, vp); std::shared_ptr<JSCallbackWrapper> tmpCobj(new JSCallbackWrapper()); tmpCobj->setJSCallbackFunc(argv[0]); if(argc >= 2) { tmpCobj->setJSCallbackThis(argv[1]); } if(argc == 3) { tmpCobj->setJSExtraData(argv[2]); } CallFuncN *ret = CallFuncN::create([=](Node* sender){ const jsval& jsvalThis = tmpCobj->getJSCallbackThis(); const jsval& jsvalCallback = tmpCobj->getJSCallbackFunc(); const jsval& jsvalExtraData = tmpCobj->getJSExtraData(); bool hasExtraData = !JSVAL_IS_VOID(jsvalExtraData); JSObject* thisObj = JSVAL_IS_VOID(jsvalThis) ? nullptr : JSVAL_TO_OBJECT(jsvalThis); JSB_AUTOCOMPARTMENT_WITH_GLOBAL_OBJCET js_proxy_t *proxy = js_get_or_create_proxy<cocos2d::Node>(cx, sender); jsval retval; if(jsvalCallback != JSVAL_VOID) { if (hasExtraData) { jsval valArr[2]; valArr[0] = OBJECT_TO_JSVAL(proxy->obj); valArr[1] = jsvalExtraData; JS_AddValueRoot(cx, valArr); JS_CallFunctionValue(cx, thisObj, jsvalCallback, 2, valArr, &retval); JS_RemoveValueRoot(cx, valArr); } else { jsval senderVal = OBJECT_TO_JSVAL(proxy->obj); JS_AddValueRoot(cx, &senderVal); JS_CallFunctionValue(cx, thisObj, jsvalCallback, 1, &senderVal, &retval); JS_RemoveValueRoot(cx, &senderVal); } } // I think the JSCallFuncWrapper isn't needed. // Since an action will be run by a cc.Node, it will be released at the Node::cleanup. // By James Chen // JSCallFuncWrapper::setTargetForNativeNode(node, (JSCallFuncWrapper *)this); }); js_proxy_t *proxy = js_get_or_create_proxy<cocos2d::CallFunc>(cx, ret); JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(proxy->obj)); JS_SetReservedSlot(proxy->obj, 0, argv[0]); if(argc > 1) { JS_SetReservedSlot(proxy->obj, 1, argv[1]); } // if(argc == 3) { // JS_SetReservedSlot(proxy->obj, 2, argv[2]); // } // test->execute(); return JS_TRUE; } JS_ReportError(cx, "Invalid number of arguments"); return JS_FALSE; }
這是JS層調用cc.CallFunc.create時,底層執行的C++函數,這里面用了一些C++11的特性,包括std::shared_ptr智能指針和lambda表達式(也很簡單,不熟悉的童鞋可以自己找資料熟悉下)。
這里面回調函數被封裝到了lambda表達式里面,通過=方式引用外部的tmpCobj變量,這種方式跟JS的閉包非常類似。依然使用JS_CallFunctionValue進行函數調用。注意,這種調用方式跟JS里面的apply方式是很類似的。
這里面有一對函數非常有趣,JS_AddValueRoot和JS_RemoveValueRoot,這兩個函數JS_CallFunctionValue調用包起來了。因為這個valArr或senderVal是在棧上臨時生成的,沒有指定對應的root。但是中間又進行了JS函數的調用,所以這兩個值可能在JS函數調用的時候被SpiderMonkey虛擬機給垃圾回收掉(可以去看看JS的垃圾回收機制原理)。于是我們需要給他們掛一個root,保護一下,不被回收掉。
二、調用代碼
先看一下構造函數
CallFuncN * CallFuncN::create(const std::function<void(Node*)> &func) { auto ret = new CallFuncN(); if (ret && ret->initWithFunction(func) ) { ret->autorelease(); return ret; } CC_SAFE_DELETE(ret); return nullptr; }
bool CallFuncN::initWithFunction(const std::function<void (Node *)> &func) { _functionN = func; return true; }
傳進來的lambda表達式被存為一個std::function<
void
(Node*)>類型。
調用代碼異常簡單,使用_functionN進行調用即可。
void CallFuncN::execute() { if (_callFuncN) { (_selectorTarget->*_callFuncN)(_target); } else if (_functionN) { _functionN(_target); } }
對比上一篇中的方式,我認為這種調用方式更加合理,因為這種調用方式,對C++層Core代碼,隱藏了腳本機制。而之前的調用方式是顯示通過腳本引擎來調用的。
看完此篇和前篇,我們仔細分析了Cocos2d-x JSB里面的回調函數的寫法,詳細對于讀者而言自己實現一個回調函數已經不是什么特別困難的事情。
在剛完成此篇的時候,突然發現有這么一個帖子,講的也是JSB回調函數,寫得很不錯,還是IAP的,可以作為額外閱讀參考:
Cocos2d-x使用iOS游戲內付費IAP(JSB篇)
還有一篇可以學習的:
JS的回調函數的參數構造注記——Web學習筆記(十八)
關于回調函數的問題,先說這些吧。
下篇繼續,我們來討論一下注冊函數的事
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。