axmol/scripting/javascript/bindings/ScriptingCore.cpp

1529 lines
49 KiB
C++
Raw Normal View History

//
// ScriptingCore.cpp
// testmonkey
//
// Created by Rolando Abarca on 3/14/12.
// Copyright (c) 2012 Zynga Inc. All rights reserved.
//
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <vector>
2012-10-19 08:44:41 +08:00
#include <map>
#include "ScriptingCore.h"
2012-10-19 08:44:41 +08:00
#include "jsdbgapi.h"
#include "cocos2d.h"
#include "cocos2d_specifics.hpp"
2012-10-19 08:44:41 +08:00
// for debug socket
#if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32)
#include <io.h>
#else
2012-10-19 08:44:41 +08:00
#include <sys/socket.h>
#include <netdb.h>
#endif
#ifdef ANDROID
#include <android/log.h>
#include <jni/JniHelper.h>
#include <netinet/in.h>
#endif
#ifdef ANDROID
#define LOG_TAG "ScriptingCore.cpp"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)
#else
#define LOGD(...) js_log(__VA_ARGS__)
#endif
js_proxy_t *_native_js_global_ht = NULL;
js_proxy_t *_js_native_global_ht = NULL;
js_type_class_t *_js_global_type_ht = NULL;
char *_js_log_buf = NULL;
std::vector<sc_register_sth> registrationList;
2012-10-19 08:44:41 +08:00
// name ~> JSScript map
std::map<std::string, JSScript*> filename_script;
// port ~> socket map
std::map<int,int> ports_sockets;
// name ~> globals
std::map<std::string, js::RootedObject*> globals;
2012-10-20 00:54:21 +08:00
static void executeJSFunctionFromReservedSpot(JSContext *cx, JSObject *obj,
jsval &dataVal, jsval &retval) {
jsval func = JS_GetReservedSlot(obj, 0);
2012-10-20 00:54:21 +08:00
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);
2012-10-20 00:54:21 +08:00
}
}
static void getTouchesFuncName(int eventType, std::string &funcName) {
switch(eventType) {
case CCTOUCHBEGAN:
funcName = "onTouchesBegan";
break;
case CCTOUCHENDED:
funcName = "onTouchesEnded";
break;
case CCTOUCHMOVED:
funcName = "onTouchesMoved";
break;
case CCTOUCHCANCELLED:
funcName = "onTouchesCancelled";
break;
}
}
static void getTouchFuncName(int eventType, std::string &funcName) {
switch(eventType) {
case CCTOUCHBEGAN:
funcName = "onTouchBegan";
break;
case CCTOUCHENDED:
funcName = "onTouchEnded";
break;
case CCTOUCHMOVED:
funcName = "onTouchMoved";
break;
case CCTOUCHCANCELLED:
funcName = "onTouchCancelled";
break;
}
}
static void rootObject(JSContext *cx, JSObject *obj) {
JS_AddNamedObjectRoot(cx, &obj, "unnamed");
}
static void unRootObject(JSContext *cx, JSObject *obj) {
JS_RemoveObjectRoot(cx, &obj);
}
2012-10-20 00:54:21 +08:00
static void getJSTouchObject(JSContext *cx, CCTouch *x, jsval &jsret) {
js_type_class_t *classType;
TypeTest<cocos2d::CCTouch> t;
uint32_t typeId = t.s_id();
HASH_FIND_INT(_js_global_type_ht, &typeId, classType);
assert(classType);
JSObject *_tmp = JS_NewObject(cx, classType->jsclass, classType->proto, classType->parentProto);
js_proxy_t *proxy, *nproxy;
JS_NEW_PROXY(proxy, x, _tmp);
void *ptr = x;
JS_GET_PROXY(nproxy, ptr);
JS_AddNamedObjectRoot(cx, &nproxy->obj, "CCTouch");
jsret = OBJECT_TO_JSVAL(_tmp);
}
static void removeJSTouchObject(JSContext *cx, CCTouch *x, jsval &jsret) {
js_proxy_t* nproxy;
js_proxy_t* jsproxy;
void *ptr = x;
JS_GET_PROXY(nproxy, ptr);
if (nproxy) {
JS_RemoveObjectRoot(cx, &nproxy->obj);
JS_GET_NATIVE_PROXY(jsproxy, nproxy->obj);
JS_REMOVE_PROXY(nproxy, jsproxy);
}
}
void ScriptingCore::executeJSFunctionWithThisObj(jsval thisObj, jsval callback,
jsval *data) {
jsval retval;
if(callback != JSVAL_VOID || thisObj != JSVAL_VOID) {
2012-10-19 08:44:41 +08:00
JS_CallFunctionValue(cx_, JSVAL_TO_OBJECT(thisObj), callback, 1, data, &retval);
}
}
2012-10-20 00:54:21 +08:00
static void executeJSFunctionWithName(JSContext *cx, JSObject *obj,
const char *funcName, jsval &dataVal,
jsval &retval) {
JSBool hasAction;
jsval temp_retval;
2012-10-20 00:54:21 +08:00
if (JS_HasProperty(cx, obj, funcName, &hasAction) && hasAction) {
if(!JS_GetProperty(cx, obj, funcName, &temp_retval)) {
return;
}
if(temp_retval == JSVAL_VOID) {
return;
}
JSAutoCompartment ac(cx, obj);
2012-10-20 00:54:21 +08:00
JS_CallFunctionName(cx, obj, funcName,
1, &dataVal, &retval);
}
2012-10-20 00:54:21 +08:00
}
void js_log(const char *format, ...) {
if (_js_log_buf == NULL) {
_js_log_buf = (char *)calloc(sizeof(char), 257);
}
va_list vl;
va_start(vl, format);
int len = vsnprintf(_js_log_buf, 256, format, vl);
va_end(vl);
if (len) {
2012-08-28 05:47:21 +08:00
CCLOG("JS: %s\n", _js_log_buf);
}
}
2012-10-19 08:44:41 +08:00
#define JSB_COMPATIBLE_WITH_COCOS2D_HTML5_BASIC_TYPES 1
void jsb_register_cocos2d_config( JSContext *_cx, JSObject *cocos2d)
{
// Config Object
JSObject *ccconfig = JS_NewObject(_cx, NULL, NULL, NULL);
// config.os: The Operating system
// osx, ios, android, windows, linux, etc..
#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
JSString *str = JS_InternString(_cx, "ios");
#elif (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
JSString *str = JS_InternString(_cx, "android");
#elif (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32)
JSString *str = JS_InternString(_cx, "windows");
#elif (CC_TARGET_PLATFORM == CC_PLATFORM_MARMALADE)
JSString *str = JS_InternString(_cx, "marmalade");
#elif (CC_TARGET_PLATFORM == CC_PLATFORM_LINUX)
JSString *str = JS_InternString(_cx, "linux");
#elif (CC_TARGET_PLATFORM == CC_PLATFORM_BADA)
JSString *str = JS_InternString(_cx, "bada");
#elif (CC_TARGET_PLATFORM == CC_PLATFORM_BLACKBERRY)
JSString *str = JS_InternString(_cx, "blackberry");
#elif (CC_TARGET_PLATFORM == CC_PLATFORM_MAC)
JSString *str = JS_InternString(_cx, "osx");
#else
JSString *str = JS_InternString(_cx, "unknown");
#endif
JS_DefineProperty(_cx, ccconfig, "os", STRING_TO_JSVAL(str), NULL, NULL, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
// config.deviceType: Device Type
// 'mobile' for any kind of mobile devices, 'desktop' for PCs, 'browser' for Web Browsers
// #if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32) || (CC_TARGET_PLATFORM == CC_PLATFORM_LINUX) || (CC_TARGET_PLATFORM == CC_PLATFORM_MAC)
// str = JS_InternString(_cx, "desktop");
// #else
2012-10-19 08:44:41 +08:00
str = JS_InternString(_cx, "mobile");
// #endif
2012-11-20 03:23:08 +08:00
JS_DefineProperty(_cx, ccconfig, "platform", STRING_TO_JSVAL(str), NULL, NULL, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
2012-10-19 08:44:41 +08:00
// config.engine: Type of renderer
// 'cocos2d', 'cocos2d-x', 'cocos2d-html5/canvas', 'cocos2d-html5/webgl', etc..
str = JS_InternString(_cx, "cocos2d-x");
JS_DefineProperty(_cx, ccconfig, "engine", STRING_TO_JSVAL(str), NULL, NULL, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
// config.arch: CPU Architecture
// i386, ARM, x86_64, web
#ifdef __LP64__
str = JS_InternString(_cx, "x86_64");
#elif defined(__arm__) || defined(__ARM_NEON__)
str = JS_InternString(_cx, "arm");
#else
str = JS_InternString(_cx, "i386");
#endif
JS_DefineProperty(_cx, ccconfig, "arch", STRING_TO_JSVAL(str), NULL, NULL, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
// config.version: Version of cocos2d + renderer
str = JS_InternString(_cx, cocos2dVersion() );
JS_DefineProperty(_cx, ccconfig, "version", STRING_TO_JSVAL(str), NULL, NULL, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
// config.usesTypedArrays
#if JSB_COMPATIBLE_WITH_COCOS2D_HTML5_BASIC_TYPES
JSBool b = JS_FALSE;
#else
JSBool b = JS_TRUE;
#endif
JS_DefineProperty(_cx, ccconfig, "usesTypedArrays", BOOLEAN_TO_JSVAL(b), NULL, NULL, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
// config.debug: Debug build ?
#if COCOS2D_DEBUG > 0
b = JS_TRUE;
#else
b = JS_FALSE;
#endif
JS_DefineProperty(_cx, ccconfig, "debug", BOOLEAN_TO_JSVAL(b), NULL, NULL, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
// Add "config" to "cc"
JS_DefineProperty(_cx, cocos2d, "config", OBJECT_TO_JSVAL(ccconfig), NULL, NULL, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
}
void registerDefaultClasses(JSContext* cx, JSObject* global) {
// first, try to get the ns
jsval nsval;
JSObject *ns;
JS_GetProperty(cx, global, "cc", &nsval);
if (nsval == JSVAL_VOID) {
ns = JS_NewObject(cx, NULL, NULL, NULL);
nsval = OBJECT_TO_JSVAL(ns);
JS_SetProperty(cx, global, "cc", &nsval);
} else {
JS_ValueToObject(cx, nsval, &ns);
}
jsb_register_cocos2d_config(cx, ns);
2012-10-20 00:54:21 +08:00
//
// Javascript controller (__jsc__)
//
JSObject *jsc = JS_NewObject(cx, NULL, NULL, NULL);
jsval jscVal = OBJECT_TO_JSVAL(jsc);
JS_SetProperty(cx, global, "__jsc__", &jscVal);
JS_DefineFunction(cx, jsc, "garbageCollect", ScriptingCore::forceGC, 0, JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_ENUMERATE );
JS_DefineFunction(cx, jsc, "dumpRoot", ScriptingCore::dumpRoot, 0, JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_ENUMERATE );
JS_DefineFunction(cx, jsc, "addGCRootObject", ScriptingCore::addRootJS, 1, JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_ENUMERATE );
JS_DefineFunction(cx, jsc, "removeGCRootObject", ScriptingCore::removeRootJS, 1, JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_ENUMERATE );
JS_DefineFunction(cx, jsc, "executeScript", ScriptingCore::executeScript, 1, JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_ENUMERATE );
// register some global functions
JS_DefineFunction(cx, global, "require", ScriptingCore::executeScript, 1, JSPROP_READONLY | JSPROP_PERMANENT);
JS_DefineFunction(cx, global, "log", ScriptingCore::log, 0, JSPROP_READONLY | JSPROP_PERMANENT);
JS_DefineFunction(cx, global, "executeScript", ScriptingCore::executeScript, 1, JSPROP_READONLY | JSPROP_PERMANENT);
JS_DefineFunction(cx, global, "forceGC", ScriptingCore::forceGC, 0, JSPROP_READONLY | JSPROP_PERMANENT);
2012-10-19 08:44:41 +08:00
// these are used in the debug socket
JS_DefineFunction(cx, global, "_socketOpen", jsSocketOpen, 1, JSPROP_READONLY | JSPROP_PERMANENT);
JS_DefineFunction(cx, global, "_socketWrite", jsSocketWrite, 1, JSPROP_READONLY | JSPROP_PERMANENT);
JS_DefineFunction(cx, global, "_socketRead", jsSocketRead, 1, JSPROP_READONLY | JSPROP_PERMANENT);
JS_DefineFunction(cx, global, "_socketClose", jsSocketClose, 1, JSPROP_READONLY | JSPROP_PERMANENT);
}
void sc_finalize(JSFreeOp *freeOp, JSObject *obj) {
return;
}
static JSClass global_class = {
"global", JSCLASS_GLOBAL_FLAGS,
JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, sc_finalize,
JSCLASS_NO_OPTIONAL_MEMBERS
};
ScriptingCore::ScriptingCore()
2012-10-19 08:44:41 +08:00
: rt_(NULL)
, cx_(NULL)
, global_(NULL)
2012-11-16 03:14:57 +08:00
, debugGlobal_(NULL)
{
// set utf8 strings internally (we don't need utf16)
JS_SetCStringsAreUTF8();
this->addRegisterCallback(registerDefaultClasses);
}
void ScriptingCore::string_report(jsval val) {
if (JSVAL_IS_NULL(val)) {
LOGD("val : (JSVAL_IS_NULL(val)");
// return 1;
} else if ((JSVAL_IS_BOOLEAN(val)) &&
(JS_FALSE == (JSVAL_TO_BOOLEAN(val)))) {
LOGD("val : (return value is JS_FALSE");
// return 1;
} else if (JSVAL_IS_STRING(val)) {
JSString *str = JS_ValueToString(this->getGlobalContext(), val);
if (NULL == str) {
LOGD("val : return string is NULL");
} else {
2012-11-16 03:14:37 +08:00
JSStringWrapper wrapper(str);
LOGD("val : return string =\n%s\n", (char *)wrapper);
}
} else if (JSVAL_IS_NUMBER(val)) {
double number;
if (JS_FALSE ==
JS_ValueToNumber(this->getGlobalContext(), val, &number)) {
LOGD("val : return number could not be converted");
} else {
LOGD("val : return number =\n%f", number);
}
}
}
2012-10-19 08:44:41 +08:00
JSBool ScriptingCore::evalString(const char *string, jsval *outVal, const char *filename, JSContext* cx, JSObject* global)
{
jsval rval;
if (cx == NULL)
cx = cx_;
if (global == NULL)
global = global_;
JSScript* script = JS_CompileScript(cx, global, string, strlen(string), filename, 1);
if (script) {
// JSAutoCompartment ac(cx, global);
JSAutoCompartment ac(cx, global);
JSBool evaluatedOK = JS_ExecuteScript(cx, global, script, &rval);
if (JS_FALSE == evaluatedOK) {
fprintf(stderr, "(evaluatedOK == JS_FALSE)\n");
}
return evaluatedOK;
}
return false;
}
void ScriptingCore::start() {
// for now just this
this->createGlobalContext();
}
void ScriptingCore::addRegisterCallback(sc_register_sth callback) {
registrationList.push_back(callback);
}
void ScriptingCore::removeAllRoots(JSContext *cx) {
js_proxy_t *current, *tmp;
HASH_ITER(hh, _js_native_global_ht, current, tmp) {
JS_RemoveObjectRoot(cx, &current->obj);
HASH_DEL(_js_native_global_ht, current);
free(current);
}
HASH_ITER(hh, _native_js_global_ht, current, tmp) {
HASH_DEL(_native_js_global_ht, current);
free(current);
}
HASH_CLEAR(hh, _js_native_global_ht);
HASH_CLEAR(hh, _native_js_global_ht);
}
void ScriptingCore::createGlobalContext() {
2012-10-19 08:44:41 +08:00
if (this->cx_ && this->rt_) {
ScriptingCore::removeAllRoots(this->cx_);
JS_DestroyContext(this->cx_);
JS_DestroyRuntime(this->rt_);
this->cx_ = NULL;
this->rt_ = NULL;
}
JS_SetCStringsAreUTF8();
2012-10-19 08:44:41 +08:00
this->rt_ = JS_NewRuntime(10 * 1024 * 1024);
this->cx_ = JS_NewContext(rt_, 10240);
JS_SetOptions(this->cx_, JSOPTION_TYPE_INFERENCE);
JS_SetVersion(this->cx_, JSVERSION_LATEST);
JS_SetOptions(this->cx_, JS_GetOptions(this->cx_) & ~JSOPTION_METHODJIT);
JS_SetOptions(this->cx_, JS_GetOptions(this->cx_) & ~JSOPTION_METHODJIT_ALWAYS);
JS_SetErrorReporter(this->cx_, ScriptingCore::reportError);
#if defined(JS_GC_ZEAL) && defined(DEBUG)
2012-12-04 17:23:08 +08:00
//JS_SetGCZeal(this->cx_, 2, JS_DEFAULT_ZEAL_FREQ);
issue #1581: JSBinding bug fixes. Some fixes of JSBinding codes: [1] Check whether the proxy was already added in JS_NEW_PROXY [2] In struct schedFunc_proxy_t, JSScheduleWrapper* --> CCArray* Reason: One js function may correspond to many targets. To debug this, you could refer to JSScheduleWrapper::dump function. It will prove that i'm right. :) [3] In ScriptingCore::cleanupSchedulesAndActions function, we must invoke unschedule for all targets and remove the proxy both in _schedFunc_target_ht and _schedTarget_native_ht, otherwise the hash tables will grow bigger and bigger, so I added a new function JSScheduleWrapper::removeAllTargetsForNatiaveNode to make this things easier. [4] To easily find out the bugs of binding codes, I add JS_SetGCZeal in ScriptingCore::createGlobalContext, it only works in DEBUG mode. [5] In js_cocos2dx_CCNode_getChildren, we should add the generated array to root to avoid gc happen when invoking JS_SetElement. [6] The JSCallFuncWrapper isn't needed since an action will be run by a cc.Node and it will be released at the CCNode::cleanup. [7] Some improvements of JSScheduleWrapper class. [8] Added a new function JSScheduleWrapper::setTarget, it's for js_CCNode_unschedule to find out which target need to be unscheduled. [9] Commented JS_SetReservedSlot in js_CCNode_scheduleOnce and js_CCNode_schedule. Reason: For js_CCNode_scheduleOnce: Don't add the callback function to the reserved slot of this js object.Since the class of js object may be inherited from cocos class(e.g. cc.Sprite). The subclass will not contain reserved slots. It will crash if invoking this. For js_CCNode_schedule: Don't add js callback function to the reserved slot of scheduler js object. Since the scheduler is an object always rooted. So the callback function might not be released when gc comes.I looked inside the implementation of cc.Node.schedule, and it doesn't use JS_SetReservedSlot there.
2012-11-28 22:04:55 +08:00
#endif
2012-10-19 08:44:41 +08:00
this->global_ = NewGlobalObject(cx_);
for (std::vector<sc_register_sth>::iterator it = registrationList.begin(); it != registrationList.end(); it++) {
sc_register_sth callback = *it;
2012-10-19 08:44:41 +08:00
callback(this->cx_, this->global_);
}
}
2012-10-19 08:44:41 +08:00
JSBool ScriptingCore::runScript(const char *path, JSObject* global, JSContext* cx)
{
if (!path) {
return false;
}
cocos2d::CCFileUtils *futil = cocos2d::CCFileUtils::sharedFileUtils();
std::string rpath;
if (path[0] == '/') {
rpath = path;
} else {
rpath = futil->fullPathFromRelativePath(path);
}
if (global == NULL) {
global = global_;
}
if (cx == NULL) {
cx = cx_;
}
2012-10-20 00:54:21 +08:00
// this will always compile the script, we can actually check if the script
// was compiled before, because it can be in the global map
2012-10-20 00:54:21 +08:00
#ifdef ANDROID
unsigned char *content = NULL;
unsigned long contentSize = 0;
2012-10-20 00:54:21 +08:00
content = (unsigned char*)CCString::createWithContentsOfFile(rpath.c_str())->getCString();
contentSize = strlen((char*)content);
2012-10-20 00:54:21 +08:00
JSScript* script = JS_CompileScript(cx, global, (char*)content, contentSize, path, 1);
#else
JSScript* script = JS_CompileUTF8File(cx, global, rpath.c_str());
#endif
JSBool evaluatedOK = false;
if (script) {
jsval rval;
filename_script[path] = script;
JSAutoCompartment ac(cx, global);
evaluatedOK = JS_ExecuteScript(cx, global, script, &rval);
if (JS_FALSE == evaluatedOK) {
CCLog("(evaluatedOK == JS_FALSE)");
}
}
return evaluatedOK;
}
ScriptingCore::~ScriptingCore()
{
removeAllRoots(cx_);
2012-10-19 08:44:41 +08:00
JS_DestroyContext(cx_);
JS_DestroyRuntime(rt_);
JS_ShutDown();
if (_js_log_buf) {
free(_js_log_buf);
_js_log_buf = NULL;
}
js_type_class_t* current, *tmp;
HASH_ITER(hh, _js_global_type_ht, current, tmp)
{
HASH_DEL(_js_global_type_ht, current);
free(current->jsclass);
free(current);
}
HASH_CLEAR(hh, _js_global_type_ht);
}
void ScriptingCore::reportError(JSContext *cx, const char *message, JSErrorReport *report)
{
js_log("%s:%u:%s\n",
report->filename ? report->filename : "<no filename=\"filename\">",
(unsigned int) report->lineno,
message);
};
JSBool ScriptingCore::log(JSContext* cx, uint32_t argc, jsval *vp)
{
if (argc > 0) {
JSString *string = NULL;
JS_ConvertArguments(cx, argc, JS_ARGV(cx, vp), "S", &string);
if (string) {
2012-11-16 03:14:37 +08:00
JSStringWrapper wrapper(string);
2012-11-22 17:34:58 +08:00
js_log((char *)wrapper);
}
}
return JS_TRUE;
}
2012-08-28 12:04:51 +08:00
void ScriptingCore::removeScriptObjectByCCObject(CCObject* pObj)
{
2012-08-28 12:04:51 +08:00
js_proxy_t* nproxy;
js_proxy_t* jsproxy;
void *ptr = (void*)pObj;
2012-08-28 12:04:51 +08:00
JS_GET_PROXY(nproxy, ptr);
if (nproxy) {
JSContext *cx = ScriptingCore::getInstance()->getGlobalContext();
JS_GET_NATIVE_PROXY(jsproxy, nproxy->obj);
JS_RemoveObjectRoot(cx, &jsproxy->obj);
2012-08-28 12:04:51 +08:00
JS_REMOVE_PROXY(nproxy, jsproxy);
}
}
JSBool ScriptingCore::setReservedSpot(uint32_t i, JSObject *obj, jsval value) {
JS_SetReservedSlot(obj, i, value);
return JS_TRUE;
}
JSBool ScriptingCore::executeScript(JSContext *cx, uint32_t argc, jsval *vp)
{
if (argc >= 1) {
jsval* argv = JS_ARGV(cx, vp);
JSString* str = JS_ValueToString(cx, argv[0]);
2012-11-16 03:14:37 +08:00
JSStringWrapper path(str);
JSBool res = false;
if (argc == 2 && argv[1].isString()) {
JSString* globalName = JSVAL_TO_STRING(argv[1]);
2012-11-16 03:14:37 +08:00
JSStringWrapper name(globalName);
js::RootedObject* rootedGlobal = globals[name];
if (rootedGlobal) {
res = ScriptingCore::getInstance()->runScript(path, rootedGlobal->get());
} else {
2012-11-16 03:14:37 +08:00
JS_ReportError(cx, "Invalid global object: %s", (char*)name);
return JS_FALSE;
}
} else {
JSObject* glob = JS_GetGlobalForScopeChain(cx);
res = ScriptingCore::getInstance()->runScript(path, glob);
}
return res;
}
return JS_TRUE;
}
JSBool ScriptingCore::forceGC(JSContext *cx, uint32_t argc, jsval *vp)
{
JSRuntime *rt = JS_GetRuntime(cx);
JS_GC(rt);
return JS_TRUE;
}
static void dumpNamedRoot(const char *name, void *addr, JSGCRootType type, void *data)
{
CCLOG("Root: '%s' at %p", name, addr);
}
JSBool ScriptingCore::dumpRoot(JSContext *cx, uint32_t argc, jsval *vp)
{
// JS_DumpNamedRoots is only available on DEBUG versions of SpiderMonkey.
// Mac and Simulator versions were compiled with DEBUG.
#if DEBUG
2012-10-19 08:44:41 +08:00
// JSContext *_cx = ScriptingCore::getInstance()->getGlobalContext();
// JSRuntime *rt = JS_GetRuntime(_cx);
// JS_DumpNamedRoots(rt, dumpNamedRoot, NULL);
#endif
return JS_TRUE;
}
JSBool ScriptingCore::addRootJS(JSContext *cx, uint32_t argc, jsval *vp)
{
if (argc == 1) {
JSObject *o = NULL;
if (JS_ConvertArguments(cx, argc, JS_ARGV(cx, vp), "o", &o) == JS_TRUE) {
if (JS_AddNamedObjectRoot(cx, &o, "from-js") == JS_FALSE) {
LOGD("something went wrong when setting an object to the root");
}
}
return JS_TRUE;
}
return JS_FALSE;
}
JSBool ScriptingCore::removeRootJS(JSContext *cx, uint32_t argc, jsval *vp)
{
if (argc == 1) {
JSObject *o = NULL;
if (JS_ConvertArguments(cx, argc, JS_ARGV(cx, vp), "o", &o) == JS_TRUE) {
JS_RemoveObjectRoot(cx, &o);
}
return JS_TRUE;
}
return JS_FALSE;
}
2012-10-20 00:54:21 +08:00
void ScriptingCore::pauseSchedulesAndActions(CCNode *node) {
CCArray * arr = JSScheduleWrapper::getTargetForNativeNode(node);
if(! arr) return;
for(unsigned int i = 0; i < arr->count(); ++i) {
if(arr->objectAtIndex(i)) {
node->getScheduler()->pauseTarget(arr->objectAtIndex(i));
}
}
}
void ScriptingCore::resumeSchedulesAndActions(CCNode *node) {
2012-10-20 00:54:21 +08:00
CCArray * arr = JSScheduleWrapper::getTargetForNativeNode(node);
if(!arr) return;
for(unsigned int i = 0; i < arr->count(); ++i) {
if(!arr->objectAtIndex(i)) continue;
node->getScheduler()->resumeTarget(arr->objectAtIndex(i));
}
}
void ScriptingCore::cleanupSchedulesAndActions(CCNode *node) {
CCArray * arr = JSCallFuncWrapper::getTargetForNativeNode(node);
if(arr) {
arr->removeAllObjects();
}
arr = JSScheduleWrapper::getTargetForNativeNode(node);
if(arr) {
issue #1581: JSBinding bug fixes. Some fixes of JSBinding codes: [1] Check whether the proxy was already added in JS_NEW_PROXY [2] In struct schedFunc_proxy_t, JSScheduleWrapper* --> CCArray* Reason: One js function may correspond to many targets. To debug this, you could refer to JSScheduleWrapper::dump function. It will prove that i'm right. :) [3] In ScriptingCore::cleanupSchedulesAndActions function, we must invoke unschedule for all targets and remove the proxy both in _schedFunc_target_ht and _schedTarget_native_ht, otherwise the hash tables will grow bigger and bigger, so I added a new function JSScheduleWrapper::removeAllTargetsForNatiaveNode to make this things easier. [4] To easily find out the bugs of binding codes, I add JS_SetGCZeal in ScriptingCore::createGlobalContext, it only works in DEBUG mode. [5] In js_cocos2dx_CCNode_getChildren, we should add the generated array to root to avoid gc happen when invoking JS_SetElement. [6] The JSCallFuncWrapper isn't needed since an action will be run by a cc.Node and it will be released at the CCNode::cleanup. [7] Some improvements of JSScheduleWrapper class. [8] Added a new function JSScheduleWrapper::setTarget, it's for js_CCNode_unschedule to find out which target need to be unscheduled. [9] Commented JS_SetReservedSlot in js_CCNode_scheduleOnce and js_CCNode_schedule. Reason: For js_CCNode_scheduleOnce: Don't add the callback function to the reserved slot of this js object.Since the class of js object may be inherited from cocos class(e.g. cc.Sprite). The subclass will not contain reserved slots. It will crash if invoking this. For js_CCNode_schedule: Don't add js callback function to the reserved slot of scheduler js object. Since the scheduler is an object always rooted. So the callback function might not be released when gc comes.I looked inside the implementation of cc.Node.schedule, and it doesn't use JS_SetReservedSlot there.
2012-11-28 22:04:55 +08:00
CCScheduler* pScheduler = CCDirector::sharedDirector()->getScheduler();
CCObject* pObj = NULL;
CCARRAY_FOREACH(arr, pObj)
{
pScheduler->unscheduleAllForTarget(pObj);
}
JSScheduleWrapper::removeAllTargetsForNatiaveNode(node);
}
}
int ScriptingCore::executeNodeEvent(CCNode* pNode, int nAction)
{
js_proxy_t * p;
JS_GET_PROXY(p, pNode);
if (!p) return 0;
jsval retval;
jsval dataVal = INT_TO_JSVAL(1);
js_proxy_t *proxy;
JS_GET_PROXY(proxy, pNode);
if(nAction == kCCNodeOnEnter)
{
2012-10-19 08:44:41 +08:00
executeJSFunctionWithName(this->cx_, p->obj, "onEnter", dataVal, retval);
resumeSchedulesAndActions(pNode);
2012-10-20 00:54:21 +08:00
}
else if(nAction == kCCNodeOnExit)
{
2012-10-19 08:44:41 +08:00
executeJSFunctionWithName(this->cx_, p->obj, "onExit", dataVal, retval);
pauseSchedulesAndActions(pNode);
}
else if(nAction == kCCNodeOnEnterTransitionDidFinish)
{
2012-10-19 08:44:41 +08:00
executeJSFunctionWithName(this->cx_, p->obj, "onEnterTransitionDidFinish", dataVal, retval);
}
else if(nAction == kCCNodeOnExitTransitionDidStart)
{
2012-10-19 08:44:41 +08:00
executeJSFunctionWithName(this->cx_, p->obj, "onExitTransitionDidStart", dataVal, retval);
}
else if(nAction == kCCNodeOnCleanup) {
cleanupSchedulesAndActions(pNode);
}
return 1;
}
int ScriptingCore::executeMenuItemEvent(CCMenuItem* pMenuItem)
{
js_proxy_t * p;
JS_GET_PROXY(p, pMenuItem);
if (!p) return 0;
jsval retval;
jsval dataVal;
js_proxy_t *proxy;
JS_GET_PROXY(proxy, pMenuItem);
dataVal = (proxy ? OBJECT_TO_JSVAL(proxy->obj) : JSVAL_NULL);
2012-10-19 08:44:41 +08:00
executeJSFunctionFromReservedSpot(this->cx_, p->obj, dataVal, retval);
return 1;
}
int ScriptingCore::executeNotificationEvent(CCNotificationCenter* pNotificationCenter, const char* pszName)
{
return 1;
}
int ScriptingCore::executeCallFuncActionEvent(CCCallFunc* pAction, CCObject* pTarget/* = NULL*/)
{
return 1;
}
int ScriptingCore::executeSchedule(CCTimer* pTimer, float dt, CCNode* pNode/* = NULL*/)
{
js_proxy_t * p;
JS_GET_PROXY(p, pNode);
if (!p) return 0;
jsval retval;
jsval dataVal = DOUBLE_TO_JSVAL(dt);
2012-10-19 08:44:41 +08:00
executeJSFunctionWithName(this->cx_, p->obj, "update", dataVal, retval);
return 1;
}
int ScriptingCore::executeLayerTouchesEvent(CCLayer* pLayer, int eventType, CCSet *pTouches)
{
std::string funcName = "";
getTouchesFuncName(eventType, funcName);
2012-10-19 08:44:41 +08:00
JSObject *jsretArr = JS_NewArrayObject(this->cx_, 0, NULL);
2012-10-19 08:44:41 +08:00
JS_AddNamedObjectRoot(this->cx_, &jsretArr, "touchArray");
int count = 0;
for(CCSetIterator it = pTouches->begin(); it != pTouches->end(); ++it, ++count) {
jsval jsret;
2012-10-19 08:44:41 +08:00
getJSTouchObject(this->cx_, (CCTouch *) *it, jsret);
if(!JS_SetElement(this->cx_, jsretArr, count, &jsret)) {
break;
}
}
executeFunctionWithObjectData(pLayer, funcName.c_str(), jsretArr);
2012-10-19 08:44:41 +08:00
JS_RemoveObjectRoot(this->cx_, &jsretArr);
for(CCSetIterator it = pTouches->begin(); it != pTouches->end(); ++it, ++count) {
jsval jsret;
2012-10-19 08:44:41 +08:00
removeJSTouchObject(this->cx_, (CCTouch *) *it, jsret);
}
return 1;
}
int ScriptingCore::executeLayerTouchEvent(CCLayer* pLayer, int eventType, CCTouch *pTouch)
{
std::string funcName = "";
getTouchFuncName(eventType, funcName);
jsval jsret;
getJSTouchObject(this->getGlobalContext(), pTouch, jsret);
JSObject *jsObj = JSVAL_TO_OBJECT(jsret);
bool retval = executeFunctionWithObjectData(pLayer, funcName.c_str(), jsObj);
removeJSTouchObject(this->getGlobalContext(), pTouch, jsret);
return retval;
}
bool ScriptingCore::executeFunctionWithObjectData(CCNode *self, const char *name, JSObject *obj) {
2012-10-20 00:54:21 +08:00
js_proxy_t * p;
JS_GET_PROXY(p, self);
if (!p) return false;
jsval retval;
jsval dataVal = OBJECT_TO_JSVAL(obj);
2012-10-20 00:54:21 +08:00
2012-10-19 08:44:41 +08:00
executeJSFunctionWithName(this->cx_, p->obj, name, dataVal, retval);
if(JSVAL_IS_NULL(retval)) {
return false;
}
else if(JSVAL_IS_BOOLEAN(retval)) {
return JSVAL_TO_BOOLEAN(retval);
}
return false;
}
int ScriptingCore::executeFunctionWithOwner(jsval owner, const char *name, jsval data) {
jsval retval;
2012-10-19 08:44:41 +08:00
executeJSFunctionWithName(this->cx_, JSVAL_TO_OBJECT(owner), name, data, retval);
return 1;
}
int ScriptingCore::executeAccelerometerEvent(CCLayer *pLayer, CCAcceleration *pAccelerationValue) {
jsval value = ccacceleration_to_jsval(this->getGlobalContext(), *pAccelerationValue);
JS_AddValueRoot(this->getGlobalContext(), &value);
executeFunctionWithObjectData(pLayer, "onAccelerometer", JSVAL_TO_OBJECT(value));
JS_RemoveValueRoot(this->getGlobalContext(), &value);
return 1;
}
int ScriptingCore::executeLayerKeypadEvent(CCLayer* pLayer, int eventType)
{
return 0;
}
2012-10-20 00:54:21 +08:00
int ScriptingCore::executeCustomTouchesEvent(int eventType,
CCSet *pTouches, JSObject *obj)
{
jsval retval;
std::string funcName;
getTouchesFuncName(eventType, funcName);
2012-10-20 00:54:21 +08:00
2012-10-19 08:44:41 +08:00
JSObject *jsretArr = JS_NewArrayObject(this->cx_, 0, NULL);
JS_AddNamedObjectRoot(this->cx_, &jsretArr, "touchArray");
int count = 0;
for(CCSetIterator it = pTouches->begin(); it != pTouches->end(); ++it, ++count) {
jsval jsret;
2012-10-19 08:44:41 +08:00
getJSTouchObject(this->cx_, (CCTouch *) *it, jsret);
if(!JS_SetElement(this->cx_, jsretArr, count, &jsret)) {
break;
}
}
2012-10-20 00:54:21 +08:00
jsval jsretArrVal = OBJECT_TO_JSVAL(jsretArr);
2012-10-19 08:44:41 +08:00
executeJSFunctionWithName(this->cx_, obj, funcName.c_str(), jsretArrVal, retval);
JS_RemoveObjectRoot(this->cx_, &jsretArr);
2012-10-20 00:54:21 +08:00
for(CCSetIterator it = pTouches->begin(); it != pTouches->end(); ++it, ++count) {
jsval jsret;
2012-10-19 08:44:41 +08:00
removeJSTouchObject(this->cx_, (CCTouch *) *it, jsret);
}
2012-10-20 00:54:21 +08:00
return 1;
}
2012-10-20 00:54:21 +08:00
int ScriptingCore::executeCustomTouchEvent(int eventType,
CCTouch *pTouch, JSObject *obj) {
jsval retval;
std::string funcName;
getTouchFuncName(eventType, funcName);
2012-10-20 00:54:21 +08:00
jsval jsTouch;
2012-10-19 08:44:41 +08:00
getJSTouchObject(this->cx_, pTouch, jsTouch);
2012-10-20 00:54:21 +08:00
2012-10-19 08:44:41 +08:00
executeJSFunctionWithName(this->cx_, obj, funcName.c_str(), jsTouch, retval);
return 1;
2012-10-20 00:54:21 +08:00
}
2012-10-20 00:54:21 +08:00
int ScriptingCore::executeCustomTouchEvent(int eventType,
CCTouch *pTouch, JSObject *obj,
jsval &retval) {
std::string funcName;
getTouchFuncName(eventType, funcName);
2012-10-20 00:54:21 +08:00
jsval jsTouch;
2012-10-19 08:44:41 +08:00
getJSTouchObject(this->cx_, pTouch, jsTouch);
2012-10-19 08:44:41 +08:00
executeJSFunctionWithName(this->cx_, obj, funcName.c_str(), jsTouch, retval);
return 1;
2012-10-20 00:54:21 +08:00
2012-10-19 08:44:41 +08:00
}
#pragma mark - Conversion Routines
long long jsval_to_long_long(JSContext *cx, jsval v) {
JSObject *tmp = JSVAL_TO_OBJECT(v);
if (JS_IsTypedArrayObject(tmp, cx) && JS_GetTypedArrayByteLength(tmp, cx) == 8) {
uint32_t *data = (uint32_t *)JS_GetUint32ArrayData(tmp, cx);
long long r = (long long)(*data);
return r;
}
return 0;
}
std::string jsval_to_std_string(JSContext *cx, jsval v) {
JSString *tmp = JS_ValueToString(cx, v);
2012-11-16 03:14:37 +08:00
JSStringWrapper ret(tmp);
return ret;
}
const char* jsval_to_c_string(JSContext *cx, jsval v) {
JSString *tmp = JS_ValueToString(cx, v);
return JS_EncodeString(cx, tmp);
}
CCPoint jsval_to_ccpoint(JSContext *cx, jsval v) {
JSObject *tmp;
jsval jsx, jsy;
double x, y;
JSBool ok = JS_ValueToObject(cx, v, &tmp) &&
JS_GetProperty(cx, tmp, "x", &jsx) &&
JS_GetProperty(cx, tmp, "y", &jsy) &&
JS_ValueToNumber(cx, jsx, &x) &&
JS_ValueToNumber(cx, jsy, &y);
assert(ok == JS_TRUE);
return cocos2d::CCPoint(x, y);
}
CCAcceleration jsval_to_ccacceleration(JSContext *cx, jsval v) {
JSObject *tmp;
jsval jsx, jsy, jsz, jstimestamp;
double x, y, timestamp, z;
JSBool ok = JS_ValueToObject(cx, v, &tmp) &&
JS_GetProperty(cx, tmp, "x", &jsx) &&
JS_GetProperty(cx, tmp, "y", &jsy) &&
JS_GetProperty(cx, tmp, "z", &jsz) &&
JS_GetProperty(cx, tmp, "timestamp", &jstimestamp) &&
JS_ValueToNumber(cx, jsx, &x) &&
JS_ValueToNumber(cx, jsy, &y) &&
JS_ValueToNumber(cx, jsz, &z) &&
JS_ValueToNumber(cx, jstimestamp, &timestamp);
assert(ok == JS_TRUE);
CCAcceleration ret = {x, y, z, timestamp};
return ret;
}
CCArray* jsvals_variadic_to_ccarray( JSContext *cx, jsval *vp, int argc)
{
JSBool ok = JS_FALSE;
CCArray* pArray = CCArray::create();
for( int i=0; i < argc; i++ )
{
double num = 0.0;
// optimization: JS_ValueToNumber is expensive. And can convert an string like "12" to a number
if( JSVAL_IS_NUMBER(*vp)) {
ok = JS_ValueToNumber(cx, *vp, &num );
if (!ok) {
break;
}
pArray->addObject(CCInteger::create((int)num));
}
else if (JSVAL_IS_STRING(*vp))
{
JSStringWrapper str(JSVAL_TO_STRING(*vp), cx);
pArray->addObject(CCString::create(str));
}
else
{
js_proxy_t* p;
JSObject* obj = JSVAL_TO_OBJECT(*vp);
JS_GET_NATIVE_PROXY(p, obj);
if (p) {
pArray->addObject((CCObject*)p->ptr);
}
}
// next
vp++;
}
return pArray;
}
CCRect jsval_to_ccrect(JSContext *cx, jsval v) {
JSObject *tmp;
jsval jsx, jsy, jswidth, jsheight;
double x, y, width, height;
JSBool ok = JS_ValueToObject(cx, v, &tmp) &&
JS_GetProperty(cx, tmp, "x", &jsx) &&
JS_GetProperty(cx, tmp, "y", &jsy) &&
JS_GetProperty(cx, tmp, "width", &jswidth) &&
JS_GetProperty(cx, tmp, "height", &jsheight) &&
JS_ValueToNumber(cx, jsx, &x) &&
JS_ValueToNumber(cx, jsy, &y) &&
JS_ValueToNumber(cx, jswidth, &width) &&
JS_ValueToNumber(cx, jsheight, &height);
assert(ok == JS_TRUE);
return cocos2d::CCRect(x, y, width, height);
}
CCSize jsval_to_ccsize(JSContext *cx, jsval v) {
JSObject *tmp;
jsval jsw, jsh;
double w, h;
JSBool ok = JS_ValueToObject(cx, v, &tmp) &&
JS_GetProperty(cx, tmp, "width", &jsw) &&
JS_GetProperty(cx, tmp, "height", &jsh) &&
JS_ValueToNumber(cx, jsw, &w) &&
JS_ValueToNumber(cx, jsh, &h);
assert(ok == JS_TRUE);
return cocos2d::CCSize(w, h);
}
ccGridSize jsval_to_ccgridsize(JSContext *cx, jsval v) {
JSObject *tmp;
jsval jsx, jsy;
double x, y;
JSBool ok = JS_ValueToObject(cx, v, &tmp) &&
JS_GetProperty(cx, tmp, "x", &jsx) &&
JS_GetProperty(cx, tmp, "y", &jsy) &&
JS_ValueToNumber(cx, jsx, &x) &&
JS_ValueToNumber(cx, jsy, &y);
assert(ok == JS_TRUE);
return cocos2d::ccg(x, y);
}
ccColor4B jsval_to_cccolor4b(JSContext *cx, jsval v) {
JSObject *tmp;
jsval jsr, jsg, jsb, jsa;
double r, g, b, a;
JSBool ok = JS_ValueToObject(cx, v, &tmp) &&
JS_GetProperty(cx, tmp, "r", &jsr) &&
JS_GetProperty(cx, tmp, "g", &jsg) &&
JS_GetProperty(cx, tmp, "b", &jsb) &&
JS_GetProperty(cx, tmp, "a", &jsa) &&
JS_ValueToNumber(cx, jsr, &r) &&
JS_ValueToNumber(cx, jsg, &g) &&
JS_ValueToNumber(cx, jsb, &b) &&
JS_ValueToNumber(cx, jsa, &a);
assert(ok == JS_TRUE);
return cocos2d::ccc4(r, g, b, a);
}
ccColor4F jsval_to_cccolor4f(JSContext *cx, jsval v) {
JSObject *tmp;
jsval jsr, jsg, jsb, jsa;
double r, g, b, a;
JSBool ok = JS_ValueToObject(cx, v, &tmp) &&
JS_GetProperty(cx, tmp, "r", &jsr) &&
JS_GetProperty(cx, tmp, "g", &jsg) &&
JS_GetProperty(cx, tmp, "b", &jsb) &&
JS_GetProperty(cx, tmp, "a", &jsa) &&
JS_ValueToNumber(cx, jsr, &r) &&
JS_ValueToNumber(cx, jsg, &g) &&
JS_ValueToNumber(cx, jsb, &b) &&
JS_ValueToNumber(cx, jsa, &a);
assert(ok == JS_TRUE);
return cocos2d::ccc4f(r, g, b, a);
}
ccColor3B jsval_to_cccolor3b(JSContext *cx, jsval v) {
JSObject *tmp;
jsval jsr, jsg, jsb;
double r, g, b;
JSBool ok = JS_ValueToObject(cx, v, &tmp) &&
JS_GetProperty(cx, tmp, "r", &jsr) &&
JS_GetProperty(cx, tmp, "g", &jsg) &&
JS_GetProperty(cx, tmp, "b", &jsb) &&
JS_ValueToNumber(cx, jsr, &r) &&
JS_ValueToNumber(cx, jsg, &g) &&
JS_ValueToNumber(cx, jsb, &b);
assert(ok == JS_TRUE);
return cocos2d::ccc3(r, g, b);
}
JSBool jsval_to_ccarray_of_CCPoint(JSContext* cx, jsval v, CCPoint **points, int *numPoints) {
// Parsing sequence
JSObject *jsobj;
JSBool ok = JS_ValueToObject( cx, v, &jsobj );
if(!jsobj || !JS_IsArrayObject( cx, jsobj)) return JS_FALSE;
2012-10-20 00:54:21 +08:00
uint32_t len;
JS_GetArrayLength(cx, jsobj, &len);
2012-10-20 00:54:21 +08:00
CCPoint *array = (CCPoint*)malloc( sizeof(CCPoint) * len);
2012-10-20 00:54:21 +08:00
for( uint32_t i=0; i< len;i++ ) {
jsval valarg;
JS_GetElement(cx, jsobj, i, &valarg);
2012-10-20 00:54:21 +08:00
array[i] = jsval_to_ccpoint(cx, valarg);
}
2012-10-20 00:54:21 +08:00
*numPoints = len;
*points = array;
2012-10-20 00:54:21 +08:00
return JS_TRUE;
}
CCArray* jsval_to_ccarray(JSContext* cx, jsval v) {
JSObject *arr;
if (JS_ValueToObject(cx, v, &arr) && JS_IsArrayObject(cx, arr)) {
uint32_t len = 0;
JS_GetArrayLength(cx, arr, &len);
CCArray* ret = CCArray::createWithCapacity(len);
for (int i=0; i < len; i++) {
jsval elt;
JSObject *elto;
if (JS_GetElement(cx, arr, i, &elt) && JS_ValueToObject(cx, elt, &elto)) {
js_proxy_t *proxy;
JS_GET_NATIVE_PROXY(proxy, elto);
if (proxy) {
ret->addObject((CCObject *)proxy->ptr);
}
}
}
return ret;
}
return NULL;
}
jsval ccarray_to_jsval(JSContext* cx, CCArray *arr) {
JSObject *jsretArr = JS_NewArrayObject(cx, 0, NULL);
for(int i = 0; i < arr->count(); ++i) {
jsval arrElement;
CCObject *obj = arr->objectAtIndex(i);
const char *type = typeid(*obj).name();
CCString *testString = dynamic_cast<cocos2d::CCString *>(obj);
CCDictionary* testDict = NULL;
CCArray* testArray = NULL;
// XXX: Only supports string, since all data read from plist files will be stored as string in cocos2d-x
// Do we need to convert string to js base type ?
if(testString) {
arrElement = c_string_to_jsval(cx, testString->getCString());
2012-11-16 03:13:21 +08:00
} else if ((testDict = dynamic_cast<cocos2d::CCDictionary*>(obj))) {
arrElement = ccdictionary_to_jsval(cx, testDict);
2012-11-16 03:13:21 +08:00
} else if ((testArray = dynamic_cast<cocos2d::CCArray*>(obj))) {
arrElement = ccarray_to_jsval(cx, testArray);
} else {
js_proxy_t *proxy = js_get_or_create_proxy<cocos2d::CCObject>(cx, obj);
arrElement = OBJECT_TO_JSVAL(proxy->obj);
}
if(!JS_SetElement(cx, jsretArr, i, &arrElement)) {
break;
}
}
return OBJECT_TO_JSVAL(jsretArr);
}
jsval ccdictionary_to_jsval(JSContext* cx, CCDictionary* dict)
{
JSObject* jsRet = JS_NewObject(cx, NULL, NULL, NULL);
CCDictElement* pElement = NULL;
CCDICT_FOREACH(dict, pElement)
{
jsval dictElement;
CCString* obj = dynamic_cast<CCString*>(pElement->getObject());
CCString *testString = dynamic_cast<cocos2d::CCString *>(obj);
CCDictionary* testDict = NULL;
CCArray* testArray = NULL;
// XXX: Only supports string, since all data read from plist files will be stored as string in cocos2d-x
// Do we need to convert string to js base type ?
if(testString) {
dictElement = c_string_to_jsval(cx, testString->getCString());
2012-11-16 03:13:21 +08:00
} else if ((testDict = dynamic_cast<cocos2d::CCDictionary*>(obj))) {
dictElement = ccdictionary_to_jsval(cx, testDict);
2012-11-16 03:13:21 +08:00
} else if ((testArray = dynamic_cast<cocos2d::CCArray*>(obj))) {
dictElement = ccarray_to_jsval(cx, testArray);
} else {
js_proxy_t *proxy = js_get_or_create_proxy<cocos2d::CCObject>(cx, obj);
dictElement = OBJECT_TO_JSVAL(proxy->obj);
}
const char* key = pElement->getStrKey();
if (key && strlen(key) > 0)
{
JS_SetProperty(cx, jsRet, key, &dictElement);
}
}
return OBJECT_TO_JSVAL(jsRet);
}
CCDictionary* jsval_to_ccdictionary(JSContext* cx, jsval v) {
JSObject *itEl = JS_NewPropertyIterator(cx, JSVAL_TO_OBJECT(v));
CCDictionary *dict = NULL;
jsid propId;
do {
jsval prop;
JS_GetPropertyById(cx, JSVAL_TO_OBJECT(v), propId, &prop);
js_proxy_t *proxy;
JSObject *tmp = JSVAL_TO_OBJECT(prop);
JS_GET_NATIVE_PROXY(proxy, tmp);
cocos2d::CCObject* cobj = (cocos2d::CCObject *)(proxy ? proxy->ptr : NULL);
TEST_NATIVE_OBJECT(cx, cobj)
jsval key;
std::string keyStr;
if(JSID_IS_STRING(propId)) {
JS_IdToValue(cx, propId, &key);
keyStr = jsval_to_std_string(cx, key);
}
if(JSVAL_IS_NULL(key)) continue;
if(!dict) {
dict = CCDictionary::create();
}
dict->setObject(cobj, keyStr);
} while(JS_NextProperty(cx, itEl, &propId));
return dict;
}
jsval long_long_to_jsval(JSContext* cx, long long v) {
JSObject *tmp = JS_NewUint32Array(cx, 2);
uint32_t *data = (uint32_t *)JS_GetArrayBufferViewData(tmp, cx);
data[0] = ((uint32_t *)(&v))[0];
data[1] = ((uint32_t *)(&v))[1];
return OBJECT_TO_JSVAL(tmp);
}
jsval std_string_to_jsval(JSContext* cx, std::string& v) {
JSString *str = JS_NewStringCopyZ(cx, v.c_str());
return STRING_TO_JSVAL(str);
}
jsval c_string_to_jsval(JSContext* cx, const char* v) {
JSString *str = JS_NewStringCopyZ(cx, v);
return STRING_TO_JSVAL(str);
}
jsval ccpoint_to_jsval(JSContext* cx, CCPoint& v) {
JSObject *tmp = JS_NewObject(cx, NULL, NULL, NULL);
if (!tmp) return JSVAL_NULL;
JSBool ok = JS_DefineProperty(cx, tmp, "x", DOUBLE_TO_JSVAL(v.x), NULL, NULL, JSPROP_ENUMERATE | JSPROP_PERMANENT) &&
JS_DefineProperty(cx, tmp, "y", DOUBLE_TO_JSVAL(v.y), NULL, NULL, JSPROP_ENUMERATE | JSPROP_PERMANENT);
if (ok) {
return OBJECT_TO_JSVAL(tmp);
}
return JSVAL_NULL;
}
jsval ccacceleration_to_jsval(JSContext* cx, CCAcceleration& v) {
JSObject *tmp = JS_NewObject(cx, NULL, NULL, NULL);
if (!tmp) return JSVAL_NULL;
JSBool ok = JS_DefineProperty(cx, tmp, "x", DOUBLE_TO_JSVAL(v.x), NULL, NULL, JSPROP_ENUMERATE | JSPROP_PERMANENT) &&
JS_DefineProperty(cx, tmp, "y", DOUBLE_TO_JSVAL(v.y), NULL, NULL, JSPROP_ENUMERATE | JSPROP_PERMANENT) &&
JS_DefineProperty(cx, tmp, "z", DOUBLE_TO_JSVAL(v.z), NULL, NULL, JSPROP_ENUMERATE | JSPROP_PERMANENT) &&
JS_DefineProperty(cx, tmp, "timestamp", DOUBLE_TO_JSVAL(v.timestamp), NULL, NULL, JSPROP_ENUMERATE | JSPROP_PERMANENT);
if (ok) {
return OBJECT_TO_JSVAL(tmp);
}
return JSVAL_NULL;
}
jsval ccrect_to_jsval(JSContext* cx, CCRect& v) {
JSObject *tmp = JS_NewObject(cx, NULL, NULL, NULL);
if (!tmp) return JSVAL_NULL;
JSBool ok = JS_DefineProperty(cx, tmp, "x", DOUBLE_TO_JSVAL(v.origin.x), NULL, NULL, JSPROP_ENUMERATE | JSPROP_PERMANENT) &&
JS_DefineProperty(cx, tmp, "y", DOUBLE_TO_JSVAL(v.origin.y), NULL, NULL, JSPROP_ENUMERATE | JSPROP_PERMANENT) &&
JS_DefineProperty(cx, tmp, "width", DOUBLE_TO_JSVAL(v.size.width), NULL, NULL, JSPROP_ENUMERATE | JSPROP_PERMANENT) &&
JS_DefineProperty(cx, tmp, "height", DOUBLE_TO_JSVAL(v.size.height), NULL, NULL, JSPROP_ENUMERATE | JSPROP_PERMANENT);
if (ok) {
return OBJECT_TO_JSVAL(tmp);
}
return JSVAL_NULL;
}
jsval ccsize_to_jsval(JSContext* cx, CCSize& v) {
JSObject *tmp = JS_NewObject(cx, NULL, NULL, NULL);
if (!tmp) return JSVAL_NULL;
JSBool ok = JS_DefineProperty(cx, tmp, "width", DOUBLE_TO_JSVAL(v.width), NULL, NULL, JSPROP_ENUMERATE | JSPROP_PERMANENT) &&
JS_DefineProperty(cx, tmp, "height", DOUBLE_TO_JSVAL(v.height), NULL, NULL, JSPROP_ENUMERATE | JSPROP_PERMANENT);
if (ok) {
return OBJECT_TO_JSVAL(tmp);
}
return JSVAL_NULL;
}
jsval ccgridsize_to_jsval(JSContext* cx, ccGridSize& v) {
JSObject *tmp = JS_NewObject(cx, NULL, NULL, NULL);
if (!tmp) return JSVAL_NULL;
JSBool ok = JS_DefineProperty(cx, tmp, "x", DOUBLE_TO_JSVAL(v.x), NULL, NULL, JSPROP_ENUMERATE | JSPROP_PERMANENT) &&
JS_DefineProperty(cx, tmp, "y", DOUBLE_TO_JSVAL(v.y), NULL, NULL, JSPROP_ENUMERATE | JSPROP_PERMANENT);
if (ok) {
return OBJECT_TO_JSVAL(tmp);
}
return JSVAL_NULL;
}
jsval cccolor4b_to_jsval(JSContext* cx, ccColor4B& v) {
JSObject *tmp = JS_NewObject(cx, NULL, NULL, NULL);
if (!tmp) return JSVAL_NULL;
JSBool ok = JS_DefineProperty(cx, tmp, "r", INT_TO_JSVAL(v.r), NULL, NULL, JSPROP_ENUMERATE | JSPROP_PERMANENT) &&
JS_DefineProperty(cx, tmp, "g", INT_TO_JSVAL(v.g), NULL, NULL, JSPROP_ENUMERATE | JSPROP_PERMANENT) &&
JS_DefineProperty(cx, tmp, "b", INT_TO_JSVAL(v.g), NULL, NULL, JSPROP_ENUMERATE | JSPROP_PERMANENT) &&
JS_DefineProperty(cx, tmp, "a", INT_TO_JSVAL(v.g), NULL, NULL, JSPROP_ENUMERATE | JSPROP_PERMANENT);
if (ok) {
return OBJECT_TO_JSVAL(tmp);
}
return JSVAL_NULL;
}
jsval cccolor4f_to_jsval(JSContext* cx, ccColor4F& v) {
JSObject *tmp = JS_NewObject(cx, NULL, NULL, NULL);
if (!tmp) return JSVAL_NULL;
JSBool ok = JS_DefineProperty(cx, tmp, "r", DOUBLE_TO_JSVAL(v.r), NULL, NULL, JSPROP_ENUMERATE | JSPROP_PERMANENT) &&
JS_DefineProperty(cx, tmp, "g", DOUBLE_TO_JSVAL(v.g), NULL, NULL, JSPROP_ENUMERATE | JSPROP_PERMANENT) &&
JS_DefineProperty(cx, tmp, "b", DOUBLE_TO_JSVAL(v.g), NULL, NULL, JSPROP_ENUMERATE | JSPROP_PERMANENT) &&
JS_DefineProperty(cx, tmp, "a", DOUBLE_TO_JSVAL(v.g), NULL, NULL, JSPROP_ENUMERATE | JSPROP_PERMANENT);
if (ok) {
return OBJECT_TO_JSVAL(tmp);
}
return JSVAL_NULL;
}
jsval cccolor3b_to_jsval(JSContext* cx, const ccColor3B& v) {
JSObject *tmp = JS_NewObject(cx, NULL, NULL, NULL);
if (!tmp) return JSVAL_NULL;
JSBool ok = JS_DefineProperty(cx, tmp, "r", INT_TO_JSVAL(v.r), NULL, NULL, JSPROP_ENUMERATE | JSPROP_PERMANENT) &&
JS_DefineProperty(cx, tmp, "g", INT_TO_JSVAL(v.g), NULL, NULL, JSPROP_ENUMERATE | JSPROP_PERMANENT) &&
JS_DefineProperty(cx, tmp, "b", INT_TO_JSVAL(v.g), NULL, NULL, JSPROP_ENUMERATE | JSPROP_PERMANENT);
if (ok) {
return OBJECT_TO_JSVAL(tmp);
}
return JSVAL_NULL;
}
2012-10-19 08:44:41 +08:00
2012-11-16 03:14:57 +08:00
#pragma mark - Debug
void ScriptingCore::enableDebugger() {
if (debugGlobal_ == NULL) {
debugGlobal_ = NewGlobalObject(cx_, true);
// these are used in the debug socket
JS_DefineFunction(cx_, debugGlobal_, "log", ScriptingCore::log, 0, JSPROP_READONLY | JSPROP_PERMANENT);
JS_DefineFunction(cx_, debugGlobal_, "_getScript", jsGetScript, 1, JSPROP_READONLY | JSPROP_PERMANENT);
JS_DefineFunction(cx_, debugGlobal_, "_socketOpen", jsSocketOpen, 1, JSPROP_READONLY | JSPROP_PERMANENT);
JS_DefineFunction(cx_, debugGlobal_, "_socketWrite", jsSocketWrite, 1, JSPROP_READONLY | JSPROP_PERMANENT);
JS_DefineFunction(cx_, debugGlobal_, "_socketRead", jsSocketRead, 1, JSPROP_READONLY | JSPROP_PERMANENT);
JS_DefineFunction(cx_, debugGlobal_, "_socketClose", jsSocketClose, 1, JSPROP_READONLY | JSPROP_PERMANENT);
runScript("debugger.js", debugGlobal_);
// prepare the debugger
do {
jsval argv = OBJECT_TO_JSVAL(global_);
jsval out;
JS_WrapObject(cx_, &debugGlobal_);
JSAutoCompartment ac(cx_, debugGlobal_);
JS_CallFunctionName(cx_, debugGlobal_, "_prepareDebugger", 1, &argv, &out);
} while (0);
// define the start debugger function
JS_DefineFunction(cx_, global_, "startDebugger", jsStartDebugger, 3, JSPROP_READONLY | JSPROP_PERMANENT);
}
}
2012-10-19 08:44:41 +08:00
2012-11-16 03:14:57 +08:00
JSBool jsStartDebugger(JSContext* cx, unsigned argc, jsval* vp)
{
JSObject* debugGlobal = ScriptingCore::getInstance()->getDebugGlobal();
if (argc == 3) {
jsval* argv = JS_ARGV(cx, vp);
jsval out;
JS_WrapObject(cx, &debugGlobal);
JSAutoCompartment ac(cx, debugGlobal);
JS_CallFunctionName(cx, debugGlobal, "_startDebugger", 3, argv, &out);
return JS_TRUE;
}
return JS_FALSE;
}
JSBool jsGetScript(JSContext* cx, unsigned argc, jsval* vp)
{
jsval* argv = JS_ARGV(cx, vp);
if (argc == 1 && argv[0].isString()) {
JSString* str = argv[0].toString();
JSStringWrapper wrapper(str);
JSScript* script = filename_script[(char *)wrapper];
if (script) {
JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL((JSObject*)script));
} else {
JS_SET_RVAL(cx, vp, JSVAL_NULL);
}
}
return JS_TRUE;
}
JSObject* NewGlobalObject(JSContext* cx, bool debug)
2012-10-19 08:44:41 +08:00
{
2012-10-20 00:54:21 +08:00
JSObject* glob = JS_NewGlobalObject(cx, &global_class, NULL);
if (!glob) {
return NULL;
}
JSAutoCompartment ac(cx, glob);
2012-10-20 00:54:21 +08:00
JSBool ok = JS_TRUE;
ok = JS_InitStandardClasses(cx, glob);
if (ok)
JS_InitReflect(cx, glob);
2012-11-16 03:14:57 +08:00
if (ok && debug)
2012-10-20 00:54:21 +08:00
ok = JS_DefineDebuggerObject(cx, glob);
if (!ok)
return NULL;
2012-10-19 08:44:41 +08:00
return glob;
2012-10-19 08:44:41 +08:00
}
// open a socket, bind it to a port and start listening, all at once :)
JSBool jsSocketOpen(JSContext* cx, unsigned argc, jsval* vp)
{
if (argc == 2) {
jsval* argv = JS_ARGV(cx, vp);
int port = JSVAL_TO_INT(argv[0]);
JSObject* callback = JSVAL_TO_OBJECT(argv[1]);
2012-10-20 00:54:21 +08:00
int s;
s = ports_sockets[port];
if (!s) {
char myname[256];
struct sockaddr_in sa;
struct hostent *hp;
memset(&sa, 0, sizeof(struct sockaddr_in));
gethostname(myname, 256);
hp = gethostbyname(myname);
sa.sin_family = hp->h_addrtype;
sa.sin_port = htons(port);
if ((s = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
JS_ReportError(cx, "error opening socket");
return JS_FALSE;
}
int optval = 1;
if ((setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*)&optval, sizeof(optval))) < 0) {
close(s);
JS_ReportError(cx, "error setting socket options");
return JS_FALSE;
}
if ((bind(s, (const struct sockaddr *)&sa, sizeof(struct sockaddr_in))) < 0) {
close(s);
JS_ReportError(cx, "error binding socket");
return JS_FALSE;
}
listen(s, 1);
int clientSocket;
if ((clientSocket = accept(s, NULL, NULL)) > 0) {
ports_sockets[port] = clientSocket;
jsval fval = OBJECT_TO_JSVAL(callback);
jsval jsSocket = INT_TO_JSVAL(clientSocket);
jsval outVal;
JS_CallFunctionValue(cx, NULL, fval, 1, &jsSocket, &outVal);
}
} else {
// just call the callback with the client socket
jsval fval = OBJECT_TO_JSVAL(callback);
jsval jsSocket = INT_TO_JSVAL(s);
jsval outVal;
JS_CallFunctionValue(cx, NULL, fval, 1, &jsSocket, &outVal);
}
JS_SET_RVAL(cx, vp, INT_TO_JSVAL(s));
}
return JS_TRUE;
2012-10-19 08:44:41 +08:00
}
JSBool jsSocketRead(JSContext* cx, unsigned argc, jsval* vp)
{
if (argc == 1) {
jsval* argv = JS_ARGV(cx, vp);
int s = JSVAL_TO_INT(argv[0]);
char buff[1024];
JSString* outStr = JS_NewStringCopyZ(cx, "");
2012-10-20 00:54:21 +08:00
2012-11-16 03:14:57 +08:00
int bytesRead;
while ((bytesRead = read(s, buff, 1024)) > 0) {
JSString* newStr = JS_NewStringCopyN(cx, buff, bytesRead);
outStr = JS_ConcatStrings(cx, outStr, newStr);
// break on new line
if (buff[bytesRead-1] == '\n') {
break;
}
}
JS_SET_RVAL(cx, vp, STRING_TO_JSVAL(outStr));
} else {
JS_SET_RVAL(cx, vp, JSVAL_NULL);
}
return JS_TRUE;
2012-10-19 08:44:41 +08:00
}
JSBool jsSocketWrite(JSContext* cx, unsigned argc, jsval* vp)
{
if (argc == 2) {
jsval* argv = JS_ARGV(cx, vp);
int s;
2012-10-20 00:54:21 +08:00
s = JSVAL_TO_INT(argv[0]);
JSString* jsstr = JS_ValueToString(cx, argv[1]);
2012-11-16 03:14:37 +08:00
JSStringWrapper str(jsstr);
2012-10-20 00:54:21 +08:00
write(s, str, strlen(str));
}
return JS_TRUE;
2012-10-19 08:44:41 +08:00
}
JSBool jsSocketClose(JSContext* cx, unsigned argc, jsval* vp)
{
if (argc == 1) {
jsval* argv = JS_ARGV(cx, vp);
int s = JSVAL_TO_INT(argv[0]);
close(s);
}
return JS_TRUE;
2012-10-19 08:44:41 +08:00
}
JSBool jsb_set_reserved_slot(JSObject *obj, uint32_t idx, jsval value)
{
JSClass *klass = JS_GetClass(obj);
unsigned int slots = JSCLASS_RESERVED_SLOTS(klass);
if( idx >= slots )
return JS_FALSE;
JS_SetReservedSlot(obj, idx, value);
return JS_TRUE;
}
JSBool jsb_get_reserved_slot(JSObject *obj, uint32_t idx, jsval& ret)
{
JSClass *klass = JS_GetClass(obj);
unsigned int slots = JSCLASS_RESERVED_SLOTS(klass);
if( idx >= slots )
return JS_FALSE;
ret = JS_GetReservedSlot(obj, idx);
return JS_TRUE;
}