COCOS JSBinding Workflow
Setup JSB Enviroment
ScriptingCore* sc = ScriptingCore::getInstance();
sc->addRegisterCallback(register_all_cocos2dx);
sc->addRegisterCallback(register_cocos2dx_js_core);
sc->addRegisterCallback(jsb_register_system);
// ......
sc->start();
sc->runScript("jsb_boot.js");initRegister
initRegisterAdd registerDefaultClasses for later Class && Functions registeration
ScriptingCore::ScriptingCore() {
initRegister();
}
void ScriptingCore::initRegister() {
this->addRegisterCallback(registerDefaultClasses);
}addRegisterCallback
addRegisterCallbackAdd Native cocos Class to list for later Class && Functions registeration
// ScriptingCore.cpp
void ScriptingCore::addRegisterCallback(sc_register_sth callback) {
registrationList.push_back(callback);
}
// sc_register_sth
typedef void (*sc_register_sth)(JSContext* cx, JS::HandleObject global);
// registrationList
static std::vector<sc_register_sth> registrationList;createGlobalContext
createGlobalContextSetup SpiderMonkey Enviroment
void ScriptingCore::createGlobalContext() {
_rt = JS_NewRuntime(8L * 1024L * 1024L);
// ...
_cx = JS_NewContext(_rt, 8192);
// ...
_global.construct(_cx);
_global.ref() = NewGlobalObject(_cx);
// ...
}jsb_prepare.js
jsb_prepare.jsRegister some Functions in javascript
// ...
var cc = cc || {};
var jsb = jsb || {};
cc.defineGetterSetter = function (proto, prop, getter, setter) {/* ... */};
var ClassManager = {/* ... */};
cc.Class = function() {/* ... */};
cc.Class.extend = function (prop) {/* ... */};
// ...Register registrationList
registrationListstd::vector<sc_register_sth>::iterator it;
for (it = registrationList.begin(); it != registrationList.end(); it++) {
sc_register_sth callback = *it;
callback(_cx, _global.ref());
}Register registerDefaultClasses
registerDefaultClassesvoid registerDefaultClasses(JSContext* cx, JS::HandleObject global) {
// ...
}Register cc namespace
cc namespaceJS::RootedValue nsval(cx);
JS::RootedObject ns(cx);
ns.set(JS_NewObject(cx, NULL, JS::NullPtr(), JS::NullPtr()));
nsval = OBJECT_TO_JSVAL(ns);
JS_SetProperty(cx, global, "cc", nsval);Register global Functions
FunctionsTo use in javascript
JS_DefineFunction(cx, global, "require", /* ... */);
JS_DefineFunction(cx, global, "log", /* ... */);
JS_DefineFunction(cx, global, "forceGC", /* ... */);
// ...Register register_all_cocos2dx
register_all_cocos2dxvoid register_all_cocos2dx(JSContext* cx, JS::HandleObject obj) {
JS::RootedObject ns(cx);
get_or_create_js_obj(cx, obj, "cc", &ns);
// ...
js_register_cocos2dx_Node(cx, ns);
js_register_cocos2dx_Sprite(cx, ns);
js_register_cocos2dx_Action(cx, ns);
// ...
}Register js_register_cocos2dx_Node
js_register_cocos2dx_NodeRegister Native cocos::Node Class with Functions and Properties
void js_register_cocos2dx_Node(JSContext *cx, JS::HandleObject global) {
jsb_cocos2d_Node_class = (JSClass *)calloc(1, sizeof(JSClass));
jsb_cocos2d_Node_class->name = "Node";
// ...
static JSFunctionSpec funcs[] = {
JS_FN("addChild", js_cocos2dx_Node_addChild, /* ... */),
JS_FN("removeChild", js_cocos2dx_Node_removeChild, /* ... */),
// ...
JS_FS_END
};
// ...
jsb_cocos2d_Node_prototype = JS_InitClass(/* ... */);
// ...
}Then we can use cc.Node in javascript
var node = new cc.Node();
node.addChild(/* ... */);And the Bind Native Function will be called:
js_cocos2dx_Node_constructor
js_cocos2dx_Node_addChild
Add to _js_global_type_map
_js_global_type_mapAdd the cocos::Node class type to _js_global_type_map for later Object creation.
JS::RootedObject proto(cx, jsb_cocos2d_Node_prototype);
jsb_register_class<cocos2d::Node>(
cx, jsb_cocos2d_Node_class, proto, JS::NullPtr()
);Register Other Native Classes
Classesjsb_boot.js
jsb_boot.jsPrepare boot enviroment in javascript
Create Cocos Object
// javascript
var sprite = new cc.Sprite("xxx.png");Call new cc.Sprite("xxx.png") from javascript
new cc.Sprite("xxx.png") from javascriptTrigger ctor in C++
ctor in C++static bool js_cocos2dx_Sprite_ctor(/**/)Create NativeObject cocos::Sprite
NativeObject cocos::Spritecocos2d::Sprite *nobj = new (std::nothrow) cocos2d::Sprite();Create JSObject
JSObject// jsb_ref_create_jsobject
JS::RootedObject proto(cx, typeClass->proto.ref());
JS::RootedObject parent(cx, typeClass->parentProto.ref());
JS::RootedObject js_obj(cx, JS_NewObject(cx, typeClass->jsclass, proto, parent));Bind NativeObject with JSObject
NativeObject with JSObjectJS::RootedObject jsobj(
cx, jsb_ref_create_jsobject(cx, cobj, typeClass, "cocos2d::Sprite")
);
// jsb_ref_create_jsobject
js_proxy_t* newproxy = jsb_new_proxy(ref, js_obj);Add JSObject to GC Root
JSObject to GC Root// jsb_ref_create_jsobject
JS::AddNamedObjectRoot(cx, js_obj, "cocos2d::Sprite");Return created Object to javascript
Object to javascriptargs.rval().set(OBJECT_TO_JSVAL(jsobj));Call _ctor in javascript if any from C++
_ctor in javascript if any from C++bool isFound = false;
if (JS_HasProperty(cx, obj, "_ctor", &isFound) && isFound)
executeFunctionWithOwner(OBJECT_TO_JSVAL(obj), "_ctor", args);jsb_create_apis.js
// jsb_create_apis.js
_p = cc.Sprite.prototype;
_p._ctor = function(fileName, rect) {
// ......
this.initWithFile(fileName);
};Trigger initWithFile in C++
initWithFile in C++bool js_cocos2dx_Sprite_initWithFile(/**/)Try to find the NativeObject (cobj) bind with the JSObject
NativeObject (cobj) bind with the JSObjectcocos2d::Sprite* cobj = nullptr;
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
JS::RootedObject obj(cx);
obj.set(args.thisv().toObjectOrNull());
js_proxy_t *proxy = jsb_get_js_proxy(obj);
cobj = (cocos2d::Sprite *)(proxy ? proxy->ptr : nullptr);
JSB_PRECONDITION2( cobj, cx, false, "js_cocos2dx_Sprite_initWithFile : Invalid Native Object");Call initWithFile on NativeObject with param
initWithFile on NativeObject with paramstd::string arg0;
ok &= jsval_to_std_string(cx, args.get(0), &arg0);
if (!ok) { ok = true; break; }
bool ret = cobj->initWithFile(arg0); // arg0 ==> "xxx.png"Remove Cocos Object
// javascript
sprite.removeFromParentAndCleanup();Call removeFromParentAndCleanup from javascript
removeFromParentAndCleanup from javascriptTrigger removeFromParentAndCleanup in C++
removeFromParentAndCleanup in C++bool js_cocos2dx_Node_removeFromParentAndCleanup(/**/)Try to find the NativeObject bind to the JSObject when created
NativeObject bind to the JSObject when createdcocos2d::Node* cobj = nullptr;
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
JS::RootedObject obj(cx);
obj.set(args.thisv().toObjectOrNull());
js_proxy_t *proxy = jsb_get_js_proxy(obj);
cobj = (cocos2d::Node *)(proxy ? proxy->ptr : nullptr);
JSB_PRECONDITION2( cobj, cx, false, "js_cocos2dx_Node_removeFromParentAndCleanup : Invalid Native Object");Call removeFromParent on NativeObject with param
removeFromParent on NativeObject with paramcobj->removeFromParent();C++ do the remove stuffs
C++ do the remove stuffsTrigger cocos::Ref destructor
cocos::Ref destructorRef::~Ref() {
// ...
ScriptEngineManager::getInstance()
->getScriptEngine()
->removeScriptObjectByObject(this);
}Trigger removeScriptObjectByObject in C++
removeScriptObjectByObject in C++void ScriptingCore::removeScriptObjectByObject(cocos2d::Ref* nativeObj)Find the JSObject bind to the NativeObject
JSObject bind to the NativeObjectauto proxy = jsb_get_native_proxy(nativeObj);Remove the JSObject from GC Root
JSObject from GC RootJS::RemoveObjectRoot(cx, &proxy->obj);Remove the proxy
proxyjsb_remove_proxy(proxy);Event Register in Cocos
// javascript
cc.eventManager.addCustomListener("game_on_show", this.onGameShow.bind(this));Call addCustomListener from javascript
addCustomListener from javascriptTrigger addCustomEventListener in C++
addCustomEventListener in C++bool js_cocos2dx_EventDispatcher_addCustomEventListener(JSContext *cx, uint32_t argc, jsval *vpCheck Function
FunctionJS_TypeOfValue(cx, args.get(1)) == JSTYPE_FUNCTIONCreate JSFunctionWrapper with this and onGameShow
JSFunctionWrapper with this and onGameShowJS::RootedObject jstarget(cx, args.thisv().toObjectOrNull());
std::shared_ptr<JSFunctionWrapper> func(new JSFunctionWrapper(cx, jstarget, args.get(1)));Create a lambda for Callback with data
lambda for Callback with dataauto lambda = [=](cocos2d::EventCustom* larg0) -> void {/**/};Create cocos2d::EventListenerCustom
cocos2d::EventListenerCustomcocos2d::EventListenerCustom* ret = cobj->addCustomEventListener(arg0, arg1);Return cocos2d::EventListenerCustom to javascript
cocos2d::EventListenerCustom to javascriptjsret = OBJECT_TO_JSVAL(
js_get_or_create_jsobject<cocos2d::EventListenerCustom> (
cx, (cocos2d::EventListenerCustom*)ret
)
args.rval().set(jsret);Event Dispatch in Cocos
// javascript
cc.eventManager.dispatchCustomEvent("game_on_show", "custom_data");Call dispatchCustomEvent from javascript
dispatchCustomEvent from javascriptCreate cc.EventCustom in javascript
cc.EventCustom in javascriptcc.eventManager.dispatchCustomEvent = function (eventName, userData) {
var ev = new cc.EventCustom(eventName);
ev.setUserData(userData);
this.dispatchEvent(ev);
};Trigger EventCustom_constructor in C++
EventCustom_constructor in C++bool js_cocos2dx_EventCustom_constructor(/* ... */) {/* ... */}Set user data in javascript
javascriptev.setUserData(optionalUserData);**User data is not passed to C++, stored in javascript
// jsb_cocos2d.js
cc.Node.prototype.setUserData = function (data) {
this.userData = data;
};Call dispatchEvent
dispatchEventthis.dispatchEvent(ev);Trigger EventDispatcher_dispatchEvent in C++
EventDispatcher_dispatchEvent in C++bool js_cocos2dx_EventDispatcher_dispatchEvent(/* ... */) {
cocos2d::Event* arg0 = nullptr;
js_proxy_t *jsProxy;
JS::RootedObject tmpObj(cx, args.get(0).toObjectOrNull());
jsProxy = jsb_get_js_proxy(tmpObj);
cobj->dispatchEvent(arg0);
}Trigger stored lamba function
lamba functionvoid {
JSB_AUTOCOMPARTMENT_WITH_GLOBAL_OBJCET
jsval largv[1];
largv[0] = OBJECT_TO_JSVAL(
js_get_or_create_jsobject<cocos2d::EventCustom>(
cx, (cocos2d::EventCustom*)larg0)
);
JS::RootedValue rval(cx);
bool succeed = func->invoke(1, &largv[0], &rval);
};JSAPI invoke called
JSAPI invoke calledextern JS_PUBLIC_API(bool)
JS_CallFunctionValue(/**/);javascript receive event with data
javascript receive event with dataonGameShow: function (event) {
event.getUserData();
}Get user data stored in javascript
javascriptcc.Node.prototype.getUserData = function () {
return this.userData;
};CallFunc in Cocos
// javascript
this.runAction(
cc.callFunc(this.onGameShow, this, "some_data")
);Call cc.callFunc in javascript
cc.callFunc in javascriptTrigger CallFunc_ctor in C++
CallFunc_ctor in C++static bool js_cocos2dx_CallFunc_ctor(/* JSNative */)
{
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
JS::RootedObject obj(cx, args.thisv().toObjectOrNull());
cocos2d::CallFunc *nobj = new (std::nothrow) cocos2d::CallFunc();
auto newproxy = jsb_new_proxy(nobj, obj);
jsb_ref_init(cx, &newproxy->obj, nobj, "cocos2d::CallFunc");
bool isFound = false;
if (JS_HasProperty(cx, obj, "_ctor", &isFound) && isFound)
executeFunctionWithOwner(OBJECT_TO_JSVAL(obj), "_ctor", args);
args.rval().setUndefined();
return true;
}Trigger _ctor in javascript
_ctor in javascriptcc.CallFunc.prototype._ctor = function(selector, selectorTarget, data) {
// ......
this.initWithFunction(selector, selectorTarget, data);
};Trigger initWithFunction in C++
initWithFunction in C++bool js_cocos2dx_CallFunc_initWithFunction(/* JSNative */) {
// ......
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
JS::RootedObject obj(cx, args.thisv().toObjectOrNull());
js_proxy_t *proxy = jsb_get_js_proxy(obj);
CallFuncN *action = (cocos2d::CallFuncN *)(proxy ? proxy->ptr : NULL);
JSB_PRECONDITION2(action, cx, false, "Invalid Native Object");
// ......
}Create JSCallbackWrapper to keep the param
JSCallbackWrapper to keep the paramtarget selector data
std::shared_ptr<JSCallbackWrapper> tmpCobj(new JSCallbackWrapper());
JS::RootedValue callback(cx, args.get(0));
tmpCobj->setJSCallbackFunc(callback);
JS::RootedValue thisObj(cx, args.get(1));
tmpCobj->setJSCallbackThis(thisObj);
JS::RootedValue data(cx, args.get(2));
tmpCobj->setJSExtraData(data);Add a lamba function to NativeObject
lamba function to NativeObjectaction->initWithFunction([=](Node* sender) {/* */});Trigger lamba from C++
lamba from C++{
JSB_AUTOCOMPARTMENT_WITH_GLOBAL_OBJCET
JS::RootedValue jsvalThis(cx, tmpCobj->getJSCallbackThis());
JS::RootedObject thisObj(cx, jsvalThis.toObjectOrNull());
JS::RootedValue jsvalCallback(cx, tmpCobj->getJSCallbackFunc());
JS::RootedValue jsvalExtraData(cx, tmpCobj->getJSExtraData());
JS::RootedValue senderVal(cx);
js_type_class_t *typeClass =
js_get_type_from_native<cocos2d::Node>(sender);
auto jsobj = jsb_ref_get_or_create_jsobject(
cx, sender, typeClass, "cocos2d::Node"
);
senderVal.set(OBJECT_TO_JSVAL(jsobj));
JS::RootedValue retval(cx);
jsval valArr[2];
valArr[0] = senderVal;
valArr[1] = jsvalExtraData;
JS::HandleValueArray callArgs =
JS::HandleValueArray::fromMarkedLocation(2, valArr);
JS_CallFunctionValue(cx, thisObj, jsvalCallback, callArgs, &retval);
}Trigger callback in javascript
callback in javascriptonGameShow: function(sender, extraData) {
/* ... */
}Schedule in Cocos
// javascript
node.schedule(this.scheduleFunc, 1);Call schedule in javascript
schedule in javascriptTrigger node_schedule in C++
node_schedule in C++bool js_CCNode_schedule(JSContext *cx, uint32_t argc, jsval *vp) {
/* ... */
}Get the Schedule Object bind to the NativeObject
Schedule Object bind to the NativeObjectJS::RootedValue thisValue(cx, args.thisv());
JS::RootedObject obj(cx, thisValue.toObjectOrNull());
js_proxy_t *proxy = jsb_get_js_proxy(obj);
cocos2d::Node *node = (cocos2d::Node *)(proxy ? proxy->ptr : NULL);
Scheduler *sched = node->getScheduler();Create a JSScheduleWrapper to keep params
JSScheduleWrapper to keep paramsJSScheduleWrapper *tmpCobj = NULL;
// ...
tmpCobj = new JSScheduleWrapper();
tmpCobj->autorelease();
tmpCobj->setJSCallbackThis(thisValue);
tmpCobj->setJSCallbackFunc(args.get(0));
tmpCobj->setTarget(node);
JSScheduleWrapper::setTargetForSchedule(args.get(0), tmpCobj);
JSScheduleWrapper::setTargetForJSObject(obj, tmpCobj);Call schedual function on the NativeObject
schedual function on the NativeObjectsched->schedule(
schedule_selector(JSScheduleWrapper::scheduleFunc),
tmpCobj,
interval, !node->isRunning()
);Trigger JSScheduleWrapper::scheduleFunc
void JSScheduleWrapper::scheduleFunc(float dt) {
// ...
JS::RootedValue callback(cx, getJSCallbackFunc());
JS::HandleValueArray args = JS::HandleValueArray::fromMarkedLocation(1, &data);
JS::RootedValue retval(cx);
JS::RootedObject callbackTarget(cx, getJSCallbackThis().toObjectOrNull());
JS_CallFunctionValue(cx, callbackTarget, callback, args, &retval);
}Trigger callback in javascript
callback in javascriptscheduleFunc:function (dt) {
// ...
}Last updated
Was this helpful?