上一篇我們講了按鍵回調(diào),這一次我們來說說各種邏輯上的回調(diào)函數(shù)。
Cocos2d-x 里面一共有三大類回調(diào)函數(shù),第一是按鍵回調(diào) CCMenu 相關(guān)的,第二類是定時(shí)器相關(guān)的回調(diào) Schedule,第三類是 Action 相關(guān)的回調(diào) CallFunc。這些回調(diào)從最初的引擎版本中就存在著,一直到現(xiàn)在。
一、綁定代碼
在 JSB 的解決方案中,對(duì)于后兩類函數(shù),引擎統(tǒng)一封裝成 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 回調(diào)函數(shù)的包裝器。三個(gè)接口也一目了然,回調(diào)函數(shù),this,外部數(shù)據(jù)。
// 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 層調(diào)用 cc.CallFunc.create 時(shí),底層執(zhí)行的 C++ 函數(shù),這里面用了一些 C++11 的特性,包括 std::shared_ptr 智能指針和 lambda 表達(dá)式(也很簡(jiǎn)單,不熟悉的童鞋可以自己找資料熟悉下)。
這里面回調(diào)函數(shù)被封裝到了lambda表達(dá)式里面,通過=方式引用外部的 tmpCobj 變量,這種方式跟 JS 的閉包非常類似。依然使用 JS_CallFunctionValue 進(jìn)行函數(shù)調(diào)用。注意,這種調(diào)用方式跟 JS 里面的 apply 方式是很類似的。
這里面有一對(duì)函數(shù)非常有趣,JS_AddValueRoot 和JS_RemoveValueRoot,這兩個(gè)函數(shù) JS_CallFunctionValue 調(diào)用包起來了。因?yàn)檫@個(gè) valArr 或 senderVal 是在棧上臨時(shí)生成的,沒有指定對(duì)應(yīng)的 root。但是中間又進(jìn)行了 JS 函數(shù)的調(diào)用,所以這兩個(gè)值可能在 JS 函數(shù)調(diào)用的時(shí)候被 SpiderMonkey 虛擬機(jī)給垃圾回收掉(可以去看看 JS 的垃圾回收機(jī)制原理)。于是我們需要給他們掛一個(gè) root,保護(hù)一下,不被回收掉。
二、調(diào)用代碼
先看一下構(gòu)造函數(shù)
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;
}
傳進(jìn)來的 lambda 表達(dá)式被存為一個(gè) std::function<void(Node*)> 類型。
調(diào)用代碼異常簡(jiǎn)單,使用 _functionN 進(jìn)行調(diào)用即可。
void CallFuncN::execute() {
if (_callFuncN) {
(_selectorTarget->*_callFuncN)(_target);
}
else if (_functionN) {
_functionN(_target);
}
}
對(duì)比上一篇中的方式,我認(rèn)為這種調(diào)用方式更加合理,因?yàn)檫@種調(diào)用方式,對(duì) C++ 層 Core 代碼,隱藏了腳本機(jī)制。而之前的調(diào)用方式是顯示通過腳本引擎來調(diào)用的。 看完此篇和前篇,我們仔細(xì)分析了 Cocos2d-x JSB 里面的回調(diào)函數(shù)的寫法,詳細(xì)對(duì)于讀者而言自己實(shí)現(xiàn)一個(gè)回調(diào)函數(shù)已經(jīng)不是什么特別困難的事情。
在剛完成此篇的時(shí)候,突然發(fā)現(xiàn)有這么一個(gè)帖子,講的也是 JSB 回調(diào)函數(shù),寫得很不錯(cuò),還是 IAP 的,可以作為額外閱讀參考:
Cocos2d-x 使用 iOS 游戲內(nèi)付費(fèi) IAP(JSB 篇)
還有一篇可以學(xué)習(xí)的:
JS 的回調(diào)函數(shù)的參數(shù)構(gòu)造注記——Web 學(xué)習(xí)筆記(十八)
關(guān)于回調(diào)函數(shù)的問題,先說這些吧。
下篇繼續(xù),我們來討論一下注冊(cè)函數(shù)的事