回调函数是界面交互和接入各种第三方SDK的关键所在,因为回调函数的C++代码是不能自动生成的,一切的一切,都需要手写完成。

比较不错的是,Cocos2d-x引擎对于回调函数提供了完整的包装机制。我们所需要做的就是了解这个机制,并使用他。学习引擎自己的代码例子,可以比较快速准确的上手这一机制。

首先,我们在Cocos2d-x 3.0 beta版中,使用他自带的工程创建工具,新建一个跨平台的JS项目。按照惯例,这是一个helloworld项目。在XCode运行时,我们可以看到:

可以看到右下角的回调按钮。我们来看看他是怎么实现的。分成两个过程来做:

一、绑定回调函数过程

首先,我们要去找回调函数JS的绑定代码,在myApp.js中,init函数里面,可以看到如下代码:

// 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函数的第三个参数,绑定了匿名回调函数。第四个参数,传入的是回调函数调用时的this(如果不理解JS的this机制,请先阅读一些JS的资料)。这些都是意图和作用很明显的JS代码,不用细说。

然后,我们去看底层对应执行的C++代码。在cocos2d_specifics.cpp文件中,找到js_cocos2dx_CCMenuItemImage_create函数。

// "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++层,这是一个重载过的函数,所以他的实现里面有很多参数个数的判断(关于重载问题请参考之前的章节)。过滤掉很多代码,我们直接看关键部分:

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];}}}}

在这里我们从参数中取出回调函数和this,分别赋值给jsCallback和jsThis。

JSObject *obj = bind_menu_item<cocos2d::MenuItemImage>(cx, ret, jsCallback, jsThis);

由这句模板函数来实现回调的绑定,四个参数依次是,JS上下文,cc.MenuItemImage对应的C++对象,回调函数,和回调函数调用时的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 <-> JSObjectjs_proxy_t *proxy = jsb_new_proxy(nativeObj, tmp);JS_AddNamedObjectRoot(cx, &proxy->obj, typeid(*nativeObj).name());       addCallBackAndThis(tmp, callback, thisObj);return tmp;}
}

继续看bind_menu_item的实现。简单说一下,因为绑定的是一个JS函数,所以实际上,需要在SpiderMonkey里面做这个绑定操作。传进来的是一个C++对象(CCMenuItemImage类型),首先找到和这个C++对象对应的JS对象。如果找不到,就新建立一个。然后通过函数addCallBackAndThis执行绑定。

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;
}

最终我们看到,存储回调函数的方法是通过SpiderMonkey的ReservedSlot机制。0位存放的是回调函数,1位存放的是回调函数对应的this。

好,到此为止,回调函数的绑定全部结束。

二、调用回调函数过程

现在我们看从C++层启动JS回调的过程。我们省略掉事件派发机制,直接看按键事件发生时的调用代码。在按键事件发生时,会调用MenuItemImage的父类MenuItem中的activate函数。该函数在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++层回调就调用。如果有脚本层(JS或lua)回调,就包装一个kMenuClickedEvent事件,然后向对应的脚本引擎发送该事件。

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进行事件分发。kMenuClickedEvent事件派发给handleMenuClickedEvent函数来处理。

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机制,取回相应的参数,最后通过JS_CallFunctionValue函数完成JS层回调函数的调用。

下篇继续

【cocos2d-x从c++到js】12:回调函数1——按键回调相关推荐

  1. 关于js的回调函数,同步回调与异步回调

    回调定义 刚开始学习javascript时,对回调函数的理解仅仅停留在知道定义阶段.什么是回调函数? 就是将一个函数作为参数传递给另一个函数,作为参数的这个函数就是回调函数. 至于为什么要用到回调函数 ...

  2. 【Android 高性能音频】AAudio 音频流 数据回调细节 ( 数据回调函数优先级 | 数据回调函数 | 采样率 | 采样数 | 缓冲区调整 | 线程不安全 )

    文章目录 I . 数据回调函数优先级 II . 数据回调函数 相关内容 III . 采样率 处理细节 IV . 数据回调函数 每次 采样个数 numFrames V . 数据回调函数 缓冲区 ( AA ...

  3. 【Android 高性能音频】AAudio 音频流 PCM 采样 的 采样 缓冲 播放 的 连续机制 ( 数据回调机制 | 数据回调函数指针 | 实现数据回调函数 | 设置数据回调函数 )

    文章目录 I . AAudio 音频流 采样 缓冲 播放 的连续机制 II . AAudio 音频流 数据回调函数 函数指针类型定义 III . AAudio 音频流 数据回调函数 实现 IV . A ...

  4. c语言timer linux 回调函数_SetTimer 与回调函数

    在控制台应用程序中,SetTimer的函数原型为: UINT_PTR SetTimer( HWND,// handle to window UINT_PTR,// timer identifier U ...

  5. ajax五种回调函数,Ajax的回调函数

    一,为什么要使用回调函数? 首先我们应该明白我们为什么要用回调函数?我们用它能解决什么样的问题. function d(){} (function(){ (function(){ var temp = ...

  6. Promise对象的resolve回调函数和reject回调函数使用

    Promise是ES6中用来结局回调地狱的问题的但是并不能帮我们减少代码量 Promise是一个构造函数 new Promise() 得到一个Promise一个实例 在Promise上有两个函数分别是 ...

  7. php数组回调函数,PHP:使用回调函数处理数组的函数

    使用回调函数处理数组的函数: 1.array_filter(); 过滤数组中的值. ** $arr=array(0,'linux',123,'hello',false,array(),'0'); $a ...

  8. c语言timer linux 回调函数_C语言回调函数详解

    1. 什么是回调函数? 回调函数,光听名字就比普通函数要高大上一些,那到底什么是回调函数呢?恕我读得书少,没有在那本书上看到关于回调函数的定义.我在百度上搜了一下,发现众说纷纭,有很大一部分都是使用类 ...

  9. vue的methods属性的方法中出现回调函数,在回调函数内部获取data中的数据

    在vue中,可能会遇到在methods属性中书写的方法中出现回调函数的情况,如以下代码: data(){return{gemometry: ""} } methods: {draw ...

最新文章

  1. vfast-全新的开始——荣新的第一天
  2. 并发数据结构-1.1.2 阻塞技术
  3. Qt split字符串分割
  4. 为何要使用docker
  5. 顺序表Sqlist.cpp
  6. 基于云原生2.0,华为云沃土云创计划全面使能伙伴创新升级
  7. linux ora 27125,ORA-27125 unable to create shared memory segment | 信春哥,系统稳,闭眼上线不回滚!...
  8. 03环信好友管理 - 删除好友
  9. 论文笔记 ACL 2020|Exploring Interpretability in Event Extraction: Multitask Learning of a Neural Event C
  10. 北京市居住证办理问题的整理
  11. css3实现3d正方体动画效果
  12. Python笔记-面向对象
  13. 别再问我为什么你敲的代码很水了!
  14. CCNET的参考文件
  15. 天花板建筑材料英国UKCA认证—EN 1364-2
  16. 一个可以给文件添加备注信息的小工具
  17. 用KooMail轻松实现本地收取Hotmail/MSN/Live邮件
  18. 算法基础之二叉树理论
  19. pypptee获取城市监测站点历史空气质量数据
  20. python猫眼电影分析_抓取猫眼电影

热门文章

  1. JeecgBoot单体升级微服务之一
  2. GIT提交记录和Revert commit过程分析
  3. 贪心算法求解背包问题
  4. 写出高效优美的C语言代码(单片机)
  5. Golang 并发编程之同步原语
  6. Linus 07年在 Google讲座介绍Git的特点和设计思路
  7. ubuntu16.04下安装codeblocks(2分钟安装)
  8. 关于Ribbon的几个问题
  9. 基于docker使用jenkins集成sonar
  10. Nginx 状态监控、缓存的两种机制(学习笔记十四)