您好,登錄后才能下訂單哦!
回調(diào)函數(shù)是界面交互和接入各種第三方SDK的關(guān)鍵所在,因為回調(diào)函數(shù)的C++代碼是不能自動生成的,一切的一切,都需要手寫完成。
比較不錯的是,Cocos2d-x引擎對于回調(diào)函數(shù)提供了完整的包裝機制。我們所需要做的就是了解這個機制,并使用他。學(xué)習(xí)引擎自己的代碼例子,可以比較快速準(zhǔn)確的上手這一機制。
首先,我們在Cocos2d-x 3.0 beta版中,使用他自帶的工程創(chuàng)建工具,新建一個跨平臺的JS項目。按照慣例,這是一個helloworld項目。在XCode運行時,我們可以看到:
可以看到右下角的回調(diào)按鈕。我們來看看他是怎么實現(xiàn)的。分成兩個過程來做:
一、綁定回調(diào)函數(shù)過程
首先,我們要去找回調(diào)函數(shù)JS的綁定代碼,在myApp.js中,init函數(shù)里面,可以看到如下代碼:
// add a "close" icon to exit the progress. it's an autorelease object var closeItem = cc.MenuItemImage.create( "res/CloseNormal.png", "res/CloseSelected.png", function () { cc.log("close button was clicked."); },this); closeItem.setAnchorPoint(cc.p(0.5, 0.5)); var menu = cc.Menu.create(closeItem); menu.setPosition(cc.p(0, 0)); this.addChild(menu, 1); closeItem.setPosition(cc.p(size.width - 20, 20));
cc.MenuItemImage.create函數(shù)的第三個參數(shù),綁定了匿名回調(diào)函數(shù)。第四個參數(shù),傳入的是回調(diào)函數(shù)調(diào)用時的this(如果不理解JS的this機制,請先閱讀一些JS的資料)。這些都是意圖和作用很明顯的JS代碼,不用細(xì)說。
然后,我們?nèi)タ吹讓訉?yīng)執(zhí)行的C++代碼。在cocos2d_specifics.cpp文件中,找到js_cocos2dx_CCMenuItemImage_create函數(shù)。
// "create" in JS // cc.MenuItemImage.create( normalImage, selectedImage, [disabledImage], callback_fn, [this] JSBool js_cocos2dx_CCMenuItemImage_create(JSContext *cx, uint32_t argc, jsval *vp) { if (argc >= 2 && argc <= 5) { jsval *argv = JS_ARGV(cx, vp); JSStringWrapper arg0(argv[0]); JSStringWrapper arg1(argv[1]); JSStringWrapper arg2; bool thirdArgIsString = true; jsval jsCallback = JSVAL_VOID; jsval jsThis = JSVAL_VOID; int last = 2; if (argc >= 3) { thirdArgIsString = argv[2].isString(); if (thirdArgIsString) { arg2.set(argv[2], cx); last = 3; } } cocos2d::MenuItemImage* ret = cocos2d::MenuItemImage::create(arg0.get(), arg1.get(), std::string(arg2.get())); if (argc >= 3) { if (!thirdArgIsString) { //cc.MenuItemImage.create( normalImage, selectedImage, callback_fn, [this] ) jsCallback = argv[last++]; if (argc == 4) { jsThis = argv[last]; } } else { //cc.MenuItemImage.create( normalImage, selectedImage, disabledImage, callback_fn, [this] ) if (argc >= 4) { jsCallback = argv[last++]; if (argc == 5) { jsThis = argv[last]; } } } } JSObject *obj = bind_menu_item<cocos2d::MenuItemImage>(cx, ret, jsCallback, jsThis); JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(obj)); return JS_TRUE; } JS_ReportError(cx, "Invalid number of arguments. Expecting: 2 <= args <= 5"); return JS_FALSE; }
因為在C++層,這是一個重載過的函數(shù),所以他的實現(xiàn)里面有很多參數(shù)個數(shù)的判斷(關(guān)于重載問題請參考之前的章節(jié))。過濾掉很多代碼,我們直接看關(guān)鍵部分:
if (argc >= 3) { if (!thirdArgIsString) { //cc.MenuItemImage.create( normalImage, selectedImage, callback_fn, [this] ) jsCallback = argv[last++]; if (argc == 4) { jsThis = argv[last]; } } else { //cc.MenuItemImage.create( normalImage, selectedImage, disabledImage, callback_fn, [this] ) if (argc >= 4) { jsCallback = argv[last++]; if (argc == 5) { jsThis = argv[last]; } } } }
在這里我們從參數(shù)中取出回調(diào)函數(shù)和this,分別賦值給jsCallback和jsThis。
JSObject *obj = bind_menu_item<cocos2d::MenuItemImage>(cx, ret, jsCallback, jsThis);
由這句模板函數(shù)來實現(xiàn)回調(diào)的綁定,四個參數(shù)依次是,JS上下文,cc.MenuItemImage對應(yīng)的C++對象,回調(diào)函數(shù),和回調(diào)函數(shù)調(diào)用時的this。
template<class T> JSObject* bind_menu_item(JSContext *cx, T* nativeObj, jsval callback, jsval thisObj) { js_proxy_t *p = jsb_get_native_proxy(nativeObj); if (p) { addCallBackAndThis(p->obj, callback, thisObj); return p->obj; } else { js_type_class_t *classType = js_get_type_from_native<T>(nativeObj); assert(classType); JSObject *tmp = JS_NewObject(cx, classType->jsclass, classType->proto, classType->parentProto); // bind nativeObj <-> JSObject js_proxy_t *proxy = jsb_new_proxy(nativeObj, tmp); JS_AddNamedObjectRoot(cx, &proxy->obj, typeid(*nativeObj).name()); addCallBackAndThis(tmp, callback, thisObj); return tmp; } }
繼續(xù)看bind_menu_item的實現(xiàn)。簡單說一下,因為綁定的是一個JS函數(shù),所以實際上,需要在SpiderMonkey里面做這個綁定操作。傳進(jìn)來的是一個C++對象(CCMenuItemImage類型),首先找到和這個C++對象對應(yīng)的JS對象。如果找不到,就新建立一個。然后通過函數(shù)addCallBackAndThis執(zhí)行綁定。
static void addCallBackAndThis(JSObject *obj, jsval callback, jsval &thisObj) { if(callback != JSVAL_VOID) { ScriptingCore::getInstance()->setReservedSpot(0, obj, callback); } if(thisObj != JSVAL_VOID) { ScriptingCore::getInstance()->setReservedSpot(1, obj, thisObj); } }
JSBool ScriptingCore::setReservedSpot(uint32_t i, JSObject *obj, jsval value) { JS_SetReservedSlot(obj, i, value); return JS_TRUE; }
最終我們看到,存儲回調(diào)函數(shù)的方法是通過SpiderMonkey的ReservedSlot機制。0位存放的是回調(diào)函數(shù),1位存放的是回調(diào)函數(shù)對應(yīng)的this。
好,到此為止,回調(diào)函數(shù)的綁定全部結(jié)束。
二、調(diào)用回調(diào)函數(shù)過程
現(xiàn)在我們看從C++層啟動JS回調(diào)的過程。我們省略掉事件派發(fā)機制,直接看按鍵事件發(fā)生時的調(diào)用代碼。在按鍵事件發(fā)生時,會調(diào)用MenuItemImage的父類MenuItem中的activate函數(shù)。該函數(shù)在CCMenuItem.cpp中。
void MenuItem::activate() { if (_enabled) { if( _callback ) { _callback(this); } if (kScriptTypeNone != _scriptType) { BasicScriptData data(this); ScriptEvent scriptEvent(kMenuClickedEvent,&data); ScriptEngineManager::getInstance()->getScriptEngine()->sendEvent(&scriptEvent); } } }
非常簡單,首先判斷按鍵是否可用。然后如果有C++層回調(diào)就調(diào)用。如果有腳本層(JS或lua)回調(diào),就包裝一個kMenuClickedEvent事件,然后向?qū)?yīng)的腳本引擎發(fā)送該事件。
int ScriptingCore::sendEvent(ScriptEvent* evt) { if (NULL == evt) return 0; JSAutoCompartment ac(_cx, _global); switch (evt->type) { case kNodeEvent: { return handleNodeEvent(evt->data); } break; case kMenuClickedEvent: { return handleMenuClickedEvent(evt->data); } break; case kTouchEvent: { return handleTouchEvent(evt->data); } break; case kTouchesEvent: { return handleTouchesEvent(evt->data); } break; case kKeypadEvent: { return handleKeypadEvent(evt->data); } break; case kAccelerometerEvent: { return handleAccelerometerEvent(evt->data); } break; default: break; } return 0; }
JS通過ScriptingCore::sendEvent進(jìn)行事件分發(fā)。kMenuClickedEvent事件派發(fā)給handleMenuClickedEvent函數(shù)來處理。
int ScriptingCore::handleMenuClickedEvent(void* data) { if (NULL == data) return 0; BasicScriptData* basicScriptData = static_cast<BasicScriptData*>(data); if (NULL == basicScriptData->nativeObject) return 0; MenuItem* menuItem = static_cast<MenuItem*>(basicScriptData->nativeObject); js_proxy_t * p = jsb_get_native_proxy(menuItem); if (!p) return 0; jsval retval; jsval dataVal; js_proxy_t *proxy = jsb_get_native_proxy(menuItem); dataVal = (proxy ? OBJECT_TO_JSVAL(proxy->obj) : JSVAL_NULL); executeJSFunctionFromReservedSpot(this->_cx, p->obj, dataVal, retval); return 1; }
static void executeJSFunctionFromReservedSpot(JSContext *cx, JSObject *obj, jsval &dataVal, jsval &retval) { jsval func = JS_GetReservedSlot(obj, 0); if (func == JSVAL_VOID) { return; } jsval thisObj = JS_GetReservedSlot(obj, 1); JSAutoCompartment ac(cx, obj); if (thisObj == JSVAL_VOID) { JS_CallFunctionValue(cx, obj, func, 1, &dataVal, &retval); } else { assert(!JSVAL_IS_PRIMITIVE(thisObj)); JS_CallFunctionValue(cx, JSVAL_TO_OBJECT(thisObj), func, 1, &dataVal, &retval); } }
再次通過SpiderMonkey的ReservedSlot機制,取回相應(yīng)的參數(shù),最后通過JS_CallFunctionValue函數(shù)完成JS層回調(diào)函數(shù)的調(diào)用。
下篇繼續(xù)
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。