初始化

This commit is contained in:
SmallMain
2022-06-25 00:23:03 +08:00
commit ef0589e8e5
2264 changed files with 617829 additions and 0 deletions

View File

@@ -0,0 +1,38 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
#include <JavaScriptCore/JavaScript.h>
#include <string>
#include <vector>
#include <unordered_map>
#include <chrono>
#include <functional>
#include <assert.h>
#include "HelperMacros.h"

View File

@@ -0,0 +1,282 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#include "Class.hpp"
#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_JSC
#include "Object.hpp"
#include "Utils.hpp"
#include "ScriptEngine.hpp"
#include "State.hpp"
namespace se {
#define JS_FN(name, func, attr) {name, func, attr}
#define JS_FS_END JS_FN(0, 0, 0)
#define JS_PSGS(name, getter, setter, attr) {name, getter, setter, attr}
#define JS_PS_END JS_PSGS(0, 0, 0, 0)
namespace {
// std::unordered_map<std::string, Class *> __clsMap;
JSContextRef __cx = nullptr;
std::vector<Class*> __allClasses;
void defaultFinalizeCallback(JSObjectRef _obj)
{
void* nativeThisObject = JSObjectGetPrivate(_obj);
if (nativeThisObject != nullptr)
{
State state(nativeThisObject);
Object* _thisObject = state.thisObject();
if (_thisObject) _thisObject->_cleanup(nativeThisObject);
JSObjectSetPrivate(_obj, nullptr);
SAFE_DEC_REF(_thisObject);
}
}
}
Class::Class()
: _parent(nullptr)
, _proto(nullptr)
, _parentProto(nullptr)
, _ctor(nullptr)
, _jsCls(nullptr)
, _finalizeOp(nullptr)
{
_jsClsDef = kJSClassDefinitionEmpty;
__allClasses.push_back(this);
}
Class::~Class()
{
}
Class* Class::create(const std::string& className, Object* obj, Object* parentProto, JSObjectCallAsConstructorCallback ctor)
{
Class* cls = new Class();
if (cls != nullptr && !cls->init(className, obj, parentProto, ctor))
{
delete cls;
cls = nullptr;
}
return cls;
}
bool Class::init(const std::string &clsName, Object* parent, Object *parentProto, JSObjectCallAsConstructorCallback ctor)
{
_name = clsName;
_parent = parent;
SAFE_INC_REF(_parent);
_parentProto = parentProto;
SAFE_INC_REF(_parentProto);
_ctor = ctor;
return true;
}
bool Class::install()
{
// assert(__clsMap.find(_name) == __clsMap.end());
//
// __clsMap.emplace(_name, this);
_jsClsDef.version = 0;
_jsClsDef.attributes = kJSClassAttributeNone;
_jsClsDef.className = _name.c_str();
if (_parentProto != nullptr)
{
_jsClsDef.parentClass = _parentProto->_getClass()->_jsCls;
}
_funcs.push_back(JS_FS_END);
// _properties.push_back(JS_PS_END);
// _jsClsDef.staticValues = _properties.data();
_jsClsDef.staticFunctions = _funcs.data();
// _jsClsDef.getProperty = _getPropertyCallback;
// _jsClsDef.setProperty = _setPropertyCallback;
// _jsClsDef.hasProperty = _hasPropertyCallback;
if (_finalizeOp != nullptr)
_jsClsDef.finalize = _finalizeOp;
else
_jsClsDef.finalize = defaultFinalizeCallback;
_jsCls = JSClassCreate(&_jsClsDef);
JSObjectRef jsCtor = JSObjectMakeConstructor(__cx, _jsCls, _ctor);
HandleObject ctorObj(Object::_createJSObject(nullptr, jsCtor));
Value functionCtor;
ScriptEngine::getInstance()->getGlobalObject()->getProperty("Function", &functionCtor);
ctorObj->setProperty("constructor", functionCtor);
JSValueRef exception = nullptr;
for (const auto& staticfunc : _staticFuncs)
{
JSStringRef name = JSStringCreateWithUTF8CString(staticfunc.name);
JSObjectRef func = JSObjectMakeFunctionWithCallback(__cx, nullptr, staticfunc.callAsFunction);
exception = nullptr;
JSObjectSetProperty(__cx, jsCtor, name, func, kJSPropertyAttributeNone, &exception);
if (exception != nullptr)
{
ScriptEngine::getInstance()->_clearException(exception);
}
JSStringRelease(name);
}
JSValueRef prototypeObj = nullptr;
JSStringRef prototypeName = JSStringCreateWithUTF8CString("prototype");
bool exist = JSObjectHasProperty(__cx, jsCtor, prototypeName);
if (exist)
{
exception = nullptr;
prototypeObj = JSObjectGetProperty(__cx, jsCtor, prototypeName, &exception);
if (exception != nullptr)
{
ScriptEngine::getInstance()->_clearException(exception);
}
}
JSStringRelease(prototypeName);
assert(prototypeObj != nullptr);
exception = nullptr;
JSObjectRef protoJSObj = JSValueToObject(__cx, prototypeObj, &exception);
if (exception != nullptr)
{
ScriptEngine::getInstance()->_clearException(exception);
}
//NOTE: I's weird that proto object has a private data which is an invalid adress.
// We have to reset its private data to the max value of unsigned long.
// Therefore, in SE_BIND_FUNC of HelperMacro.h, we could distinguish whether it's a
// proto object. Don't set it to nullptr since static method will get private data
// with nullptr. This line is needed by Web Inspector because it needs to extend
// all global JS values including global proto object. However, proto objects will
// not have a private data which will cause crash while debugging in Safari since
// se::State::nativeThisObject() will return nullptr.
JSObjectSetPrivate(protoJSObj, (void*)std::numeric_limits<unsigned long>::max());
_proto = Object::_createJSObject(this, protoJSObj);
_proto->root();
// reset constructor
_proto->setProperty("constructor", Value(ctorObj));
// Set instance properties
for (const auto& property : _properties)
{
internal::defineProperty(_proto, property.name, property.getter, property.setter);
}
// Set class properties
for (const auto& property : _staticProperties)
{
internal::defineProperty(ctorObj.get(), property.name, property.getter, property.setter);
}
_parent->setProperty(_name.c_str(), Value(ctorObj));
return true;
}
bool Class::defineFunction(const char *name, JSObjectCallAsFunctionCallback func)
{
JSStaticFunction cb = JS_FN(name, func, kJSPropertyAttributeNone);
_funcs.push_back(cb);
return true;
}
bool Class::defineProperty(const char *name, JSObjectCallAsFunctionCallback getter, JSObjectCallAsFunctionCallback setter)
{
JSPropertySpec property = JS_PSGS(name, getter, setter, kJSPropertyAttributeNone);
_properties.push_back(property);
return true;
}
bool Class::defineStaticFunction(const char *name, JSObjectCallAsFunctionCallback func)
{
JSStaticFunction cb = JS_FN(name, func, kJSPropertyAttributeNone);
_staticFuncs.push_back(cb);
return true;
}
bool Class::defineStaticProperty(const char *name, JSObjectCallAsFunctionCallback getter, JSObjectCallAsFunctionCallback setter)
{
JSPropertySpec property = JS_PSGS(name, getter, setter, kJSPropertyAttributeNone);
_staticProperties.push_back(property);
return true;
}
bool Class::defineFinalizeFunction(JSObjectFinalizeCallback func)
{
_finalizeOp = func;
return true;
}
JSObjectRef Class::_createJSObjectWithClass(Class* cls)
{
return JSObjectMake(__cx, cls->_jsCls, nullptr);
}
void Class::setContext(JSContextRef cx)
{
__cx = cx;
}
Object *Class::getProto()
{
return _proto;
}
void Class::destroy()
{
SAFE_DEC_REF(_parent);
SAFE_DEC_REF(_proto);
SAFE_DEC_REF(_parentProto);
JSClassRelease(_jsCls);
}
void Class::cleanup()
{
for (auto cls : __allClasses)
{
cls->destroy();
}
ScriptEngine::getInstance()->addAfterCleanupHook([](){
for (auto cls : __allClasses)
{
delete cls;
}
__allClasses.clear();
});
}
} // namespace se {
#endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_JSC

View File

@@ -0,0 +1,158 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
#include "../config.hpp"
#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_JSC
#include "Base.h"
namespace se {
class Object;
/**
* se::Class represents a definition of how to create a native binding object.
*/
class Class final
{
public:
/**
* @brief Creates a class used for creating relevant native binding objects.
* @param[in] className A null-terminated UTF8 string containing the class's name.
* @param[in] obj The object that current class proto object attaches to. Should not be nullptr.
* @param[in] parentProto The parent proto object that current class inherits from. Passing nullptr means a new class has no parent.
* @param[in] ctor A callback to invoke when your constructor is used in a 'new' expression. Pass nullptr to use the default object constructor.
* @return A class instance used for creating relevant native binding objects.
* @note Don't need to delete the pointer return by this method, it's managed internally.
*/
static Class* create(const std::string& className, Object* obj, Object* parentProto, JSObjectCallAsConstructorCallback ctor);
/**
* @brief Defines a member function with a callback. Each objects created by class will have this function property.
* @param[in] name A null-terminated UTF8 string containing the function name.
* @param[in] func A callback to invoke when the property is called as a function.
* @return true if succeed, otherwise false.
*/
bool defineFunction(const char *name, JSObjectCallAsFunctionCallback func);
/**
* @brief Defines a property with accessor callbacks. Each objects created by class will have this property.
* @param[in] name A null-terminated UTF8 string containing the property name.
* @param[in] getter A callback to invoke when the property is read.
* @param[in] setter A callback to invoke when the property is set.
* @return true if succeed, otherwise false.
*/
bool defineProperty(const char *name, JSObjectCallAsFunctionCallback getter, JSObjectCallAsFunctionCallback setter);
/**
* @brief Defines a static function with a callback. Only JavaScript constructor object will have this function.
* @param[in] name A null-terminated UTF8 string containing the function name.
* @param[in] func A callback to invoke when the constructor's property is called as a function.
* @return true if succeed, otherwise false.
*/
bool defineStaticFunction(const char *name, JSObjectCallAsFunctionCallback func);
/**
* @brief Defines a static property with accessor callbacks. Only JavaScript constructor object will have this property.
* @param[in] name A null-terminated UTF8 string containing the property name.
* @param[in] getter A callback to invoke when the constructor's property is read.
* @param[in] setter A callback to invoke when the constructor's property is set.
* @return true if succeed, otherwise false.
*/
bool defineStaticProperty(const char *name, JSObjectCallAsFunctionCallback getter, JSObjectCallAsFunctionCallback setter);
/**
* @brief Defines the finalize function with a callback.
* @param[in] func The callback to invoke when a JavaScript object is garbage collected.
* @return true if succeed, otherwise false.
*/
bool defineFinalizeFunction(JSObjectFinalizeCallback func);
/**
* @brief Installs class to JavaScript VM.
* @return true if succeed, otherwise false.
* @note After this method, an object could be created by `var foo = new Foo();`.
*/
bool install();
/**
* @brief Gets the proto object of this class.
* @return The proto object of this class.
* @note Don't need to be released in user code.
*/
Object* getProto();
/**
* @brief Gets the class name.
* @return The class name.
*/
const char* getName() const { return _name.c_str(); }
private:
Class();
~Class();
bool init(const std::string& clsName, Object* obj, Object* parentProto, JSObjectCallAsConstructorCallback ctor);
void destroy();
static JSObjectRef _createJSObjectWithClass(Class* cls);
static void setContext(JSContextRef cx);
static void cleanup();
struct JSPropertySpec
{
const char* name;
JSObjectCallAsFunctionCallback getter;
JSObjectCallAsFunctionCallback setter;
JSPropertyAttributes attributes;
};
std::string _name;
Object* _parent;
Object* _proto;
Object* _parentProto;
JSObjectCallAsConstructorCallback _ctor;
JSClassRef _jsCls;
JSClassDefinition _jsClsDef;
std::vector<JSStaticFunction> _funcs;
std::vector<JSStaticFunction> _staticFuncs;
std::vector<JSPropertySpec> _properties;
std::vector<JSPropertySpec> _staticProperties;
JSObjectFinalizeCallback _finalizeOp;
friend class ScriptEngine;
friend class Object;
};
} // namespace se {
#endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_JSC

View File

@@ -0,0 +1,32 @@
#import <Foundation/Foundation.h>
#import <JavaScriptCore/JavaScriptCore.h>
#ifdef __cplusplus
extern "C" {
#endif
NSString *JSValueToNSString( JSContextRef ctx, JSValueRef v );
JSValueRef NSStringToJSValue( JSContextRef ctx, NSString *string );
double JSValueToNumberFast( JSContextRef ctx, JSValueRef v );
void JSValueUnprotectSafe( JSContextRef ctx, JSValueRef v );
JSValueRef NSObjectToJSValue( JSContextRef ctx, NSObject *obj );
NSObject *JSValueToNSObject( JSContextRef ctx, JSValueRef value );
static inline void *JSValueGetPrivate(JSValueRef v) {
// On 64bit systems we can not safely call JSObjectGetPrivate with any
// JSValueRef. Doing so with immediate values (numbers, null, bool,
// undefined) will crash the app. So we check for these first.
#if __LP64__
return !((int64_t)v & 0xffff000000000002ll)
? JSObjectGetPrivate((JSObjectRef)v)
: NULL;
#else
return JSObjectGetPrivate((JSObjectRef)v);
#endif
}
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,173 @@
#import "EJConvert.h"
NSString *JSValueToNSString( JSContextRef ctx, JSValueRef v ) {
JSStringRef jsString = JSValueToStringCopy( ctx, v, NULL );
if( !jsString ) return nil;
NSString *string = (NSString *)JSStringCopyCFString( kCFAllocatorDefault, jsString );
[string autorelease];
JSStringRelease( jsString );
return string;
}
JSValueRef NSStringToJSValue( JSContextRef ctx, NSString *string ) {
JSStringRef jstr = JSStringCreateWithCFString((CFStringRef)string);
JSValueRef ret = JSValueMakeString(ctx, jstr);
JSStringRelease(jstr);
return ret;
}
// JSValueToNumberFast blindly assumes that the given JSValueRef is a
// a number. Everything else will be silently converted to 0.
// This functions comes in a 64bit and 32bit flavor, since the NaN-Boxing
// in JSC works a bit differently on each platforms. For an explanation
// of the taggging refer to JSC/runtime/JSCJSValue.h
// The 32bit version just calls the normal JSValueToNumber() function
// and is thus a lot slower.
double JSValueToNumberFast(JSContextRef ctx, JSValueRef v) {
#if __LP64__ // arm64 version
union {
int64_t asInt64;
double asDouble;
struct { int32_t asInt; int32_t tag; } asBits;
} taggedValue = { .asInt64 = (int64_t)v };
#define DoubleEncodeOffset 0x1000000000000ll
#define TagTypeNumber 0xffff0000
#define ValueTrue 0x7
if( (taggedValue.asBits.tag & TagTypeNumber) == TagTypeNumber ) {
return taggedValue.asBits.asInt;
}
else if( taggedValue.asBits.tag & TagTypeNumber ) {
taggedValue.asInt64 -= DoubleEncodeOffset;
return taggedValue.asDouble;
}
else if( taggedValue.asBits.asInt == ValueTrue ) {
return 1.0;
}
else {
return 0; // false, undefined, null, object
}
#else // armv7 version
return JSValueToNumber(ctx, v, NULL);
#endif
}
void JSValueUnprotectSafe( JSContextRef ctx, JSValueRef v ) {
if( ctx && v ) {
JSValueUnprotect(ctx, v);
}
}
JSValueRef NSObjectToJSValue( JSContextRef ctx, NSObject *obj ) {
JSValueRef ret = NULL;
// String
if( [obj isKindOfClass:NSString.class] ) {
ret = NSStringToJSValue(ctx, (NSString *)obj);
}
// Number or Bool
else if( [obj isKindOfClass:NSNumber.class] ) {
NSNumber *number = (NSNumber *)obj;
if( strcmp(number.objCType, @encode(BOOL)) == 0 ) {
ret = JSValueMakeBoolean(ctx, number.boolValue);
}
else {
ret = JSValueMakeNumber(ctx, number.doubleValue);
}
}
// Date
else if( [obj isKindOfClass:NSDate.class] ) {
NSDate *date = (NSDate *)obj;
JSValueRef timestamp = JSValueMakeNumber(ctx, date.timeIntervalSince1970 * 1000.0);
ret = JSObjectMakeDate(ctx, 1, &timestamp, NULL);
}
// Array
else if( [obj isKindOfClass:NSArray.class] ) {
NSArray *array = (NSArray *)obj;
JSValueRef *args = malloc(array.count * sizeof(JSValueRef));
for( int i = 0; i < array.count; i++ ) {
args[i] = NSObjectToJSValue(ctx, array[i] );
}
ret = JSObjectMakeArray(ctx, array.count, args, NULL);
free(args);
}
// Dictionary
else if( [obj isKindOfClass:NSDictionary.class] ) {
NSDictionary *dict = (NSDictionary *)obj;
ret = JSObjectMake(ctx, NULL, NULL);
for( NSString *key in dict ) {
JSStringRef jsKey = JSStringCreateWithUTF8CString(key.UTF8String);
JSValueRef value = NSObjectToJSValue(ctx, dict[key]);
JSObjectSetProperty(ctx, (JSObjectRef)ret, jsKey, value, NULL, NULL);
JSStringRelease(jsKey);
}
}
return ret ? ret : JSValueMakeNull(ctx);
}
NSObject *JSValueToNSObject( JSContextRef ctx, JSValueRef value ) {
JSType type = JSValueGetType(ctx, value);
switch( type ) {
case kJSTypeString: return JSValueToNSString(ctx, value);
case kJSTypeBoolean: return [NSNumber numberWithBool:JSValueToBoolean(ctx, value)];
case kJSTypeNumber: return [NSNumber numberWithDouble:JSValueToNumberFast(ctx, value)];
case kJSTypeNull: return nil;
case kJSTypeUndefined: return nil;
case kJSTypeObject: break;
}
if( type == kJSTypeObject ) {
JSObjectRef jsObj = (JSObjectRef)value;
// Get the Array constructor to check if this Object is an Array
JSStringRef arrayName = JSStringCreateWithUTF8CString("Array");
JSObjectRef arrayConstructor = (JSObjectRef)JSObjectGetProperty(ctx, JSContextGetGlobalObject(ctx), arrayName, NULL);
JSStringRelease(arrayName);
if( JSValueIsInstanceOfConstructor(ctx, jsObj, arrayConstructor, NULL) ) {
// Array
JSStringRef lengthName = JSStringCreateWithUTF8CString("length");
int count = JSValueToNumberFast(ctx, JSObjectGetProperty(ctx, jsObj, lengthName, NULL));
JSStringRelease(lengthName);
NSMutableArray *array = [NSMutableArray arrayWithCapacity:count];
for( int i = 0; i < count; i++ ) {
NSObject *obj = JSValueToNSObject(ctx, JSObjectGetPropertyAtIndex(ctx, jsObj, i, NULL));
[array addObject:(obj ? obj : NSNull.null)];
}
return array;
}
else {
// Plain Object
JSPropertyNameArrayRef properties = JSObjectCopyPropertyNames(ctx, jsObj);
size_t count = JSPropertyNameArrayGetCount(properties);
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:count];
for( size_t i = 0; i < count; i++ ) {
JSStringRef jsName = JSPropertyNameArrayGetNameAtIndex(properties, i);
NSObject *obj = JSValueToNSObject(ctx, JSObjectGetProperty(ctx, jsObj, jsName, NULL));
NSString *name = (NSString *)JSStringCopyCFString( kCFAllocatorDefault, jsName );
dict[name] = obj ? obj : NSNull.null;
[name release];
}
JSPropertyNameArrayRelease(properties);
return dict;
}
}
return nil;
}

View File

@@ -0,0 +1,83 @@
#import <Foundation/Foundation.h>
#import <JavaScriptCore/JavaScriptCore.h>
#ifdef __cplusplus
extern "C" {
#endif
/*!
@enum JSType
@abstract A constant identifying the Typed Array type of a JSValue.
@constant kEJJSTypedArrayTypeNone Not a Typed Array.
@constant kEJJSTypedArrayTypeInt8Array Int8Array
@constant kEJJSTypedArrayTypeInt16Array Int16Array
@constant kEJJSTypedArrayTypeInt32Array Int32Array
@constant kEJJSTypedArrayTypeUint8Array Int8Array
@constant kEJJSTypedArrayTypeUint8ClampedArray Int8ClampedArray
@constant kEJJSTypedArrayTypeUint16Array Uint16Array
@constant kEJJSTypedArrayTypeUint32Array Uint32Array
@constant kEJJSTypedArrayTypeFloat32Array Float32Array
@constant kEJJSTypedArrayTypeFloat64Array Float64Array
@constant kEJJSTypedArrayTypeArrayBuffer ArrayBuffer
*/
typedef enum {
kEJJSTypedArrayTypeNone = 0,
kEJJSTypedArrayTypeInt8Array = 1,
kEJJSTypedArrayTypeInt16Array = 2,
kEJJSTypedArrayTypeInt32Array = 3,
kEJJSTypedArrayTypeUint8Array = 4,
kEJJSTypedArrayTypeUint8ClampedArray = 5,
kEJJSTypedArrayTypeUint16Array = 6,
kEJJSTypedArrayTypeUint32Array = 7,
kEJJSTypedArrayTypeFloat32Array = 8,
kEJJSTypedArrayTypeFloat64Array = 9,
kEJJSTypedArrayTypeArrayBuffer = 10
} EJJSTypedArrayType;
/*!
@function
@abstract Setup the JSContext for use of the Typed Array functions.
@param ctx The execution context to use
*/
void EJJSContextPrepareTypedArrayAPI(JSContextRef ctx);
/*!
@function
@abstract Returns a JavaScript value's Typed Array type
@param ctx The execution context to use.
@param value The JSObject whose Typed Array type you want to obtain.
@result A value of type EJJSTypedArrayType that identifies value's Typed Array type
*/
EJJSTypedArrayType EJJSObjectGetTypedArrayType(JSContextRef ctx, JSObjectRef object);
/*!
@function
@abstract Creates an empty JavaScript Typed Array with the given number of elements
@param ctx The execution context to use.
@param arrayType A value of type EJJSTypedArrayType identifying the type of array you want to create
@param numElements The number of elements for the array.
@result A JSObjectRef that is a Typed Array or NULL if there was an error
*/
JSObjectRef EJJSObjectMakeTypedArray(JSContextRef ctx, EJJSTypedArrayType arrayType, size_t numElements);
/*!
@function
@abstract Returns a copy of the Typed Array's data
@param ctx The execution context to use.
@param value The JSObject whose Typed Array data you want to obtain.
@result A copy of the Typed Array's data or NULL if the JSObject is not a Typed Array
*/
NSMutableData *EJJSObjectGetTypedArrayData(JSContextRef ctx, JSObjectRef object);
/*!
@function
@abstract Replaces a Typed Array's data
@param ctx The execution context to use.
@param value The JSObject whose Typed Array data you want to replace
*/
void EJJSObjectSetTypedArrayData(JSContextRef ctx, JSObjectRef object, NSData *data);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,394 @@
#import "EJConvertTypedArray.h"
#import "EJConvert.h"
#if defined __ARM_NEON__
#include <arm_neon.h>
#endif
// These functions deal with getting and setting a JavaScript Typed Array's data.
// The process in which this is done is extremely backwards, prohibitively
// memory inefficient and painfully slow.
// JavaScriptCore still doesn't have an API to deal with Typed Arrays directly
// and nobody seems to care. See this WebKit bug for the details:
// https://bugs.webkit.org/show_bug.cgi?id=120112
// The naive method of getting the array's data would be to read the "length"
// property and call JSObjectGetPropertyAtIndex() for each element. This function
// returns a JSValue and since the array's data is just a raw memory pointer
// internally, this JSValue has be constructed first by JSC. Afterwards this
// JSValue has to be converted back into an int and stored in a native buffer.
// This ordeal takes about 1200ms per megabyte on an iPhone5S.
// So, instead of extracting each byte on its own, we create Int32 view on the
// Array, so that we can extract 4 bytes at a time. Now, the fastest method to
// get to the values is to call a function with .apply(null, theInt32Array).
// This causes the typed array to be converted into plain JSValues - we can then
// iterate all function arguments and convert each JSValue into a Int32.
// There's one big caveat, though: a function can only be called with 64k
// arguments at once, or it will blow the stack. So we have to divide the Int32
// Array into smaller subarrays and convert the data in chunks.
// Setting the Array's data works about the same. We can create a plain JS
// Array in reasonable time by treating the data as 32bit ints. This plain
// Array can then be used to call .set() on an Int32 View of the Typed Array.
// To be able to quickly determine the type of a typed array, we install an
// enum with the type id ("__ejTypedArrayType") onto each array constructor's
// prototype.
// This all brings the time down to about 7ms per megabyte for reading and about
// 20ms per megabyte for writing. Even though this 7ms/MB number may not sound
// all too bad, keep in mind that it's still 7ms slower than what we would have
// with a better API.
// Array Types to constructor names. We need to obtain the Constructors from
// the JSGlobalObject when creating Typed Arrays and when attaching our methods
// to them.
const static char *ConstructorNames[] = {
[kEJJSTypedArrayTypeNone] = NULL,
[kEJJSTypedArrayTypeInt8Array] = "Int8Array",
[kEJJSTypedArrayTypeInt16Array] = "Int16Array",
[kEJJSTypedArrayTypeInt32Array] = "Int32Array",
[kEJJSTypedArrayTypeUint8Array] = "Uint8Array",
[kEJJSTypedArrayTypeUint8ClampedArray] = "Uint8ClampedArray",
[kEJJSTypedArrayTypeUint16Array] = "Uint16Array",
[kEJJSTypedArrayTypeUint32Array] = "Uint32Array",
[kEJJSTypedArrayTypeFloat32Array] = "Float32Array",
[kEJJSTypedArrayTypeFloat64Array] = "Float64Array",
[kEJJSTypedArrayTypeArrayBuffer] = "ArrayBuffer"
};
// Data from Typed Arrays smaller than CopyInChunksThreshold will be extracted
// one byte at a time.
const static int CopyInChunksThreshold = 32;
// For large arrays, copy the data in Chunks of 16k elements, so that we don't
// blow the stack.
const static int CopyChunkSize = 0x4000;
// Hobo version to get an Int32 from a JSValueRef. This is even faster than
// JSValueToNumberFast() because it blindly assumes the value is an Int32.
// A number stored as double will produce a bogus Int value. JSC stores all
// values that are outside of the Int32 range as double.
// This function is only fast on 64bit systems. On 32bit systems we can't
// easily access the integer value.
// See JavaScriptCore/runtime/JSCJSValue.h for an explanation of the NaN-
// boxing used in JSC.
// Use with extreme caution!
static inline int32_t GetInt32(JSContextRef ctx, JSValueRef v) {
#if __LP64__
return (int32_t)(0x00000000ffffffffll & (uint64_t)v);
#else
return JSValueToNumber(ctx, v, NULL);
#endif
}
// Hobo version to create an Int32 fast. Falls back to JSValueMakeNumber()
// on 32bit systems.
static inline JSValueRef MakeInt32(JSContextRef ctx, int32_t number) {
#if __LP64__
return (0xffff000000000000ll | (uint64_t)number);
#else
return JSValueMakeNumber(ctx, number);
#endif
}
// Shorthand to get a property by name
static JSValueRef GetPropertyNamed(JSContextRef ctx, JSObjectRef object, const char *name) {
JSStringRef jsPropertyName = JSStringCreateWithUTF8CString(name);
JSValueRef value = JSObjectGetProperty(ctx, object, jsPropertyName, NULL);
JSStringRelease(jsPropertyName);
return value;
}
// Shorthand to get the Constructor for the given EJJSTypedArrayType
static JSObjectRef GetConstructor(JSContextRef ctx, EJJSTypedArrayType type) {
if( type <= kEJJSTypedArrayTypeNone || type > kEJJSTypedArrayTypeArrayBuffer ) {
return NULL;
}
const char *constructorName = ConstructorNames[type];
JSObjectRef global = JSContextGetGlobalObject(ctx);
return (JSObjectRef)GetPropertyNamed(ctx, global, constructorName);
}
// Create a typed array view from another typed array or arraybuffer
static JSObjectRef GetView(JSContextRef ctx, JSObjectRef object, EJJSTypedArrayType type, size_t count) {
EJJSTypedArrayType currentType = EJJSObjectGetTypedArrayType(ctx, object);
if( currentType == kEJJSTypedArrayTypeNone ) {
return NULL;
}
else if( currentType == type ) {
return object;
}
JSValueRef args[3];
if( currentType == kEJJSTypedArrayTypeArrayBuffer ) {
args[0] = object;
args[1] = MakeInt32(ctx, 0);
args[2] = MakeInt32(ctx, (int)count);
}
else {
args[0] = GetPropertyNamed(ctx, object, "buffer");
args[1] = GetPropertyNamed(ctx, object, "byteOffset");
args[2] = MakeInt32(ctx, (int)count);
}
JSObjectRef constructor = GetConstructor(ctx, type);
return JSObjectCallAsConstructor(ctx, constructor, 3, args, NULL);
}
// The callback called from JSObjectGetTypedArrayData with chunks of data. This writes
// into the state's data.
typedef struct {
int32_t *currentDataPtr;
JSObjectRef jsGetCallback;
JSObjectRef jsGetCallbackApply;
} AppendDataCallbackState;
static JSValueRef AppendDataCallback(
JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject,
size_t argc, const JSValueRef argv[], JSValueRef* exception
) {
AppendDataCallbackState *state = JSObjectGetPrivate(thisObject);
int32_t *dst = state->currentDataPtr;
int remainderStart = 0;
// On 64bit systems where ARM_NEON instructions are available we can use
// some SIMD intrinsics to extract the lower 32bit of each JSValueRef.
// Hopefully the JSValueRef encodes an Int32 so the lower 32bit corresponds
// to that Int32 exactly.
#if __LP64__ && defined __ARM_NEON__
// Iterate over the arguments in packs of 4.
// Load arguments as 2 lanes of 4 int32 values and store the first
// lane (the lower 32bit) into the dst buffer.
int argPacks4 = (int)argc/4;
int32_t *src = (int32_t*)argv;
for(int i = 0; i < argPacks4; i++ ) {
const int32x4x2_t lanes32 = vld2q_s32(src);
vst1q_s32(dst, lanes32.val[0]);
src += 8;
dst += 4;
}
remainderStart = argPacks4 * 4;
#endif
for( int i = remainderStart; i < argc; i++ ) {
*(dst++) = GetInt32(ctx, argv[i]);
}
state->currentDataPtr += argc;
return MakeInt32(ctx, (int)argc);
}
// To store the AppendDataCallbackState and access it _per context_, we create
// a plain JSObject where we can store a pointer to the state in the private
// data.
static void FinalizeAppendDataCallbackState(JSObjectRef object) {
AppendDataCallbackState *state = JSObjectGetPrivate(object);
free(state);
}
static JSObjectRef CreateAppendDataCallbackState(JSContextRef ctx) {
// Create a JS function that calls the AppendDataCallback
JSObjectRef jsAppendDataCallback = JSObjectMakeFunctionWithCallback(ctx, NULL, AppendDataCallback);
JSValueProtect(ctx, jsAppendDataCallback);
// Allocate and create the state and attach the AppendDataCallback
// function and its .apply property.
AppendDataCallbackState *state = malloc(sizeof(AppendDataCallbackState));
state->currentDataPtr = NULL;
state->jsGetCallback = jsAppendDataCallback;
state->jsGetCallbackApply = (JSObjectRef)GetPropertyNamed(ctx, jsAppendDataCallback, "apply");
// Create the empty js object that holds the state in its Private data
JSClassDefinition internalStateClassDef = kJSClassDefinitionEmpty;
internalStateClassDef.finalize = FinalizeAppendDataCallbackState;
JSClassRef internalStateClass = JSClassCreate(&internalStateClassDef);
JSObjectRef internalStateObject = JSObjectMake(ctx, internalStateClass, state);
JSClassRelease(internalStateClass);
return internalStateObject;
}
void EJJSContextPrepareTypedArrayAPI(JSContextRef ctx) {
// The __ejTypedArrayType property is read only, not enumerable
JSPropertyAttributes attributes =
kJSPropertyAttributeReadOnly |
kJSPropertyAttributeDontEnum |
kJSPropertyAttributeDontDelete;
// Install the __ejTypedArrayType property on each Typed Array prototype
JSStringRef jsTypeName = JSStringCreateWithUTF8CString("__ejTypedArrayType");
for( int type = kEJJSTypedArrayTypeInt8Array; type <= kEJJSTypedArrayTypeArrayBuffer; type++ ) {
JSObjectRef jsConstructor = GetConstructor(ctx, type);
JSObjectRef jsPrototype = (JSObjectRef)GetPropertyNamed(ctx, jsConstructor, "prototype");
JSValueRef jsType = MakeInt32(ctx, type);
JSObjectSetProperty(ctx, jsPrototype, jsTypeName, jsType, attributes, NULL);
}
JSStringRelease(jsTypeName);
// Create the state object for the AppendData Callback and attach it to the global
// object
JSObjectRef jsCallbackStateObject = CreateAppendDataCallbackState(ctx);
JSStringRef jsInternalStateName = JSStringCreateWithUTF8CString("__ejTypedArrayState");
JSObjectRef global = JSContextGetGlobalObject(ctx);
JSObjectSetProperty(ctx, global, jsInternalStateName, jsCallbackStateObject, attributes, NULL);
JSStringRelease(jsInternalStateName);
}
EJJSTypedArrayType EJJSObjectGetTypedArrayType(JSContextRef ctx, JSObjectRef object) {
JSValueRef jsType = GetPropertyNamed(ctx, object, "__ejTypedArrayType");
return jsType ? GetInt32(ctx, jsType) : kEJJSTypedArrayTypeNone;
}
JSObjectRef EJJSObjectMakeTypedArray(JSContextRef ctx, EJJSTypedArrayType arrayType, size_t numElements) {
JSObjectRef jsConstructor = GetConstructor(ctx, arrayType);
if( !jsConstructor ) {
return NULL;
}
JSValueRef jsNumElements = MakeInt32(ctx, (int)numElements);
return JSObjectCallAsConstructor(ctx, jsConstructor, 1, (JSValueRef[]){jsNumElements}, NULL);
}
NSMutableData *EJJSObjectGetTypedArrayData(JSContextRef ctx, JSObjectRef object) {
size_t length = GetInt32(ctx, GetPropertyNamed(ctx, object, "byteLength"));
if( !length ) {
return NULL;
}
size_t int32Count = length / 4;
size_t uint8Count = length % 4;
// For very small typed arrays it's faster to read all bytes individually
// instead of doing the callback dance.
if( length < CopyInChunksThreshold ) {
int32Count = 0;
uint8Count = length;
}
NSMutableData *data = [NSMutableData dataWithLength:length];
// Read data in large chunks of 32bit values
if( int32Count ) {
JSObjectRef int32View = GetView(ctx, object, kEJJSTypedArrayTypeInt32Array, int32Count);
if( !int32View ) {
return NULL;
}
JSObjectRef jsState = (JSObjectRef)GetPropertyNamed(ctx, JSContextGetGlobalObject(ctx), "__ejTypedArrayState");
AppendDataCallbackState *state = JSObjectGetPrivate(jsState);
state->currentDataPtr = data.mutableBytes;
// If the whole data is smaller than the chunk size, we only have to call our callback once, otherwise
// we have to create subarrays and call the callback with these.
if( int32Count < CopyChunkSize ) {
JSValueRef getArgs[] = {jsState, int32View};
JSObjectCallAsFunction(ctx, state->jsGetCallbackApply, state->jsGetCallback, 2, getArgs, NULL);
}
else {
JSObjectRef subarrayFunc = (JSObjectRef)GetPropertyNamed(ctx, int32View, "subarray");
for( int i = 0; i < int32Count; i+= CopyChunkSize) {
JSValueRef subarrayArgs[] = {MakeInt32(ctx, i), MakeInt32(ctx, i + CopyChunkSize)};
JSObjectRef jsSubarray = (JSObjectRef)JSObjectCallAsFunction(ctx, subarrayFunc, int32View, 2, subarrayArgs, NULL);
JSValueRef getArgs[] = {jsState, jsSubarray};
JSObjectCallAsFunction(ctx, state->jsGetCallbackApply, state->jsGetCallback, 2, getArgs, NULL);
}
}
}
// Read remaining bytes directly
if( uint8Count ) {
uint8_t *values8 = data.mutableBytes;
JSObjectRef uint8View = GetView(ctx, object, kEJJSTypedArrayTypeUint8Array, length);
for( int i = 0; i < uint8Count; i++ ) {
int index = (int)int32Count * 4 + i;
values8[index] = GetInt32(ctx, JSObjectGetPropertyAtIndex(ctx, uint8View, index, NULL));
}
}
return data;
}
void EJJSObjectSetTypedArrayData(JSContextRef ctx, JSObjectRef object, NSData *data) {
size_t int32Count = data.length / 4;
size_t uint8Count = data.length % 4;
if( int32Count ) {
JSObjectRef int32View = GetView(ctx, object, kEJJSTypedArrayTypeInt32Array, int32Count);
if( !int32View ) {
return;
}
// Construct the JSValues and create the plain JSArray
JSValueRef *jsValues = malloc(int32Count * sizeof(JSValueRef));
const int32_t *values32 = data.bytes;
for(int i = 0; i < int32Count; i++) {
jsValues[i] = MakeInt32(ctx, values32[i]);
}
JSObjectRef jsArray = JSObjectMakeArray(ctx, int32Count, jsValues, NULL);
free(jsValues);
// Call the .set() function on the Typed Array
JSObjectRef setFunction = (JSObjectRef)GetPropertyNamed(ctx, int32View, "set");
JSObjectCallAsFunction(ctx, setFunction, int32View, 1, (JSValueRef[]){jsArray}, NULL);
}
// Set remaining bytes directly
if( uint8Count ) {
const uint8_t *values8 = data.bytes;
JSObjectRef uint8View = GetView(ctx, object, kEJJSTypedArrayTypeUint8Array, data.length);
for( int i = 0; i < uint8Count; i++ ) {
int index = (int)int32Count * 4 + i;
JSObjectSetPropertyAtIndex(ctx, uint8View, index, MakeInt32(ctx, values8[index]), NULL);
}
}
}

View File

@@ -0,0 +1,223 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
#include "../config.hpp"
#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_JSC
#ifdef __GNUC__
#define SE_UNUSED __attribute__ ((unused))
#else
#define SE_UNUSED
#endif
#define SAFE_INC_REF(obj) if (obj != nullptr) obj->incRef()
#define SAFE_DEC_REF(obj) if ((obj) != nullptr) { (obj)->decRef(); (obj) = nullptr; }
#define _SE(name) name##Registry
#define SE_DECLARE_FUNC(funcName) \
JSValueRef funcName##Registry(JSContextRef _cx, JSObjectRef _function, JSObjectRef _thisObject, size_t argc, const JSValueRef _argv[], JSValueRef* _exception)
#define SE_BIND_FUNC(funcName) \
JSValueRef funcName##Registry(JSContextRef _cx, JSObjectRef _function, JSObjectRef _thisObject, size_t _argc, const JSValueRef _argv[], JSValueRef* _exception) \
{ \
unsigned short argc = (unsigned short) _argc; \
JSValueRef _jsRet = JSValueMakeUndefined(_cx); \
void* nativeThisObject = se::internal::getPrivate(_thisObject); \
if (nativeThisObject != (void*)std::numeric_limits<unsigned long>::max()) \
{ \
bool ret = true; \
se::ValueArray args; \
se::internal::jsToSeArgs(_cx, argc, _argv, &args); \
se::State state(nativeThisObject, args); \
ret = funcName(state); \
if (!ret) { \
SE_LOGE("[ERROR] Failed to invoke %s, location: %s:%d\n", #funcName, __FILE__, __LINE__); \
} \
se::internal::seToJsValue(_cx, state.rval(), &_jsRet); \
} \
return _jsRet; \
}
#define SE_BIND_FINALIZE_FUNC(funcName) \
void funcName##Registry(JSObjectRef _obj) \
{ \
auto se = se::ScriptEngine::getInstance(); \
se->_setGarbageCollecting(true); \
void* nativeThisObject = JSObjectGetPrivate(_obj); \
if (nativeThisObject != nullptr) \
{ \
bool ret = false; \
se::State state(nativeThisObject); \
se::Object* _thisObject = state.thisObject(); \
if (_thisObject) _thisObject->_cleanup(nativeThisObject); \
ret = funcName(state); \
if (!ret) { \
SE_LOGE("[ERROR] Failed to invoke %s, location: %s:%d\n", #funcName, __FILE__, __LINE__); \
} \
JSObjectSetPrivate(_obj, nullptr); \
SAFE_DEC_REF(_thisObject); \
} \
se->_setGarbageCollecting(false); \
}
#define SE_DECLARE_FINALIZE_FUNC(funcName) \
void funcName##Registry(JSObjectRef _obj);
// NOTE: se::Object::createObjectWithClass(cls) will return a se::Object pointer which is watched by garbage collector.
// If there is a '_ctor' function of current class, '_property.toObject->call(...)' will be invoked which is an operation that may
// make garbage collector to mark the created JS object as a garbage and set it to an invalid state.
// If this happens, crash will be triggered. So please take care of the value returned from se::Object::createObjectWithClass.
// HOW TO FIX: Use a rooted se::Value to save the se::Object poiner returned by se::Object::createObjectWithClass.
#define SE_BIND_CTOR(funcName, cls, finalizeCb) \
JSObjectRef funcName##Registry(JSContextRef _cx, JSObjectRef _constructor, size_t argc, const JSValueRef _argv[], JSValueRef* _exception) \
{ \
bool ret = true; \
se::ValueArray args; \
se::internal::jsToSeArgs(_cx, argc, _argv, &args); \
se::Value thisVal(se::Object::createObjectWithClass(cls), true); \
se::Object* thisObject = thisVal.toObject(); \
JSValueRef _jsRet = JSValueMakeUndefined(_cx); \
se::State state(thisObject, args); \
ret = funcName(state); \
if (ret) \
{ \
_jsRet = thisObject->_getJSObject(); \
se::Value _property; \
bool _found = false; \
_found = thisObject->getProperty("_ctor", &_property); \
if (_found) _property.toObject()->call(args, thisObject); \
} \
else \
{ \
SE_LOGE("[ERROR] Failed to invoke %s, location: %s:%d\n", #funcName, __FILE__, __LINE__); \
} \
return JSValueToObject(_cx, _jsRet, nullptr); \
}
#define SE_BIND_SUB_CLS_CTOR(funcName, cls, finalizeCb) \
JSValueRef funcName##Registry(JSContextRef _cx, JSObjectRef _function, JSObjectRef _thisObject, size_t argc, const JSValueRef _argv[], JSValueRef* _exception) \
{ \
bool ret = true; \
JSValueRef _jsRet = JSValueMakeUndefined(_cx); \
se::ValueArray args; \
se::internal::jsToSeArgs(_cx, argc, _argv, &args); \
se::Object* thisObject = se::Object::_createJSObject(cls, _thisObject); \
thisObject->_setFinalizeCallback(_SE(finalizeCb)); \
se::State state(thisObject, args); \
ret = funcName(state); \
if (ret) \
{ \
se::Value _property; \
bool _found = false; \
_found = thisObject->getProperty("_ctor", &_property); \
if (_found) _property.toObject()->call(args, thisObject); \
} \
else \
{ \
SE_LOGE("[ERROR] Failed to invoke %s, location: %s:%d\n", #funcName, __FILE__, __LINE__); \
} \
return _jsRet; \
}
#define SE_BIND_PROP_GET(funcName) \
JSValueRef funcName##Registry(JSContextRef _cx, JSObjectRef _function, JSObjectRef _thisObject, size_t argc, const JSValueRef _argv[], JSValueRef* _exception) \
{ \
assert(argc == 0); \
JSValueRef _jsRet = JSValueMakeUndefined(_cx); \
void* nativeThisObject = se::internal::getPrivate(_thisObject); \
if (nativeThisObject != (void*)std::numeric_limits<unsigned long>::max()) \
{ \
se::State state(nativeThisObject); \
if (funcName(state)) \
{ \
se::internal::seToJsValue(_cx, state.rval(), &_jsRet); \
} \
else \
{ \
SE_LOGE("[ERROR] Failed to invoke %s, location: %s:%d\n", #funcName, __FILE__, __LINE__); \
} \
} \
return _jsRet; \
}
#define SE_BIND_PROP_SET(funcName) \
JSValueRef funcName##Registry(JSContextRef _cx, JSObjectRef _function, JSObjectRef _thisObject, size_t argc, const JSValueRef _argv[], JSValueRef* _exception) \
{ \
assert(argc == 1); \
JSValueRef _jsRet = JSValueMakeUndefined(_cx); \
void* nativeThisObject = se::internal::getPrivate(_thisObject); \
if (nativeThisObject != (void*)std::numeric_limits<unsigned long>::max()) \
{ \
bool ret = true; \
se::Value data; \
se::internal::jsToSeValue(_cx, _argv[0], &data); \
se::ValueArray args; \
args.push_back(std::move(data)); \
se::State state(nativeThisObject, args); \
ret = funcName(state); \
if (!ret) { \
SE_LOGE("[ERROR] Failed to invoke %s, location: %s:%d\n", #funcName, __FILE__, __LINE__); \
} \
} \
return _jsRet; \
}
#define SE_TYPE_NAME(t) typeid(t).name()
#define SE_QUOTEME_(x) #x
#define SE_QUOTEME(x) SE_QUOTEME_(x)
//IDEA: implement this macro
#define SE_REPORT_ERROR(fmt, ...) SE_LOGE("[ERROR] (" __FILE__ ", " SE_QUOTEME(__LINE__) "): " fmt "\n", ##__VA_ARGS__)
#if COCOS2D_DEBUG > 0
#define SE_ASSERT(cond, fmt, ...) \
do \
{ \
if (!(cond)) \
{ \
SE_LOGE("ASSERT (" __FILE__ ", " SE_QUOTEME(__LINE__) "): " fmt "\n", ##__VA_ARGS__); \
assert(false); \
} \
} while(false)
#else
#define SE_ASSERT(cond, fmt, ...)
#endif // #if COCOS2D_DEBUG > 0
#endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_JSC

View File

@@ -0,0 +1,424 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
#include "../config.hpp"
#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_JSC
#include "Base.h"
#include "../Value.hpp"
#include "../RefCounter.hpp"
namespace se {
class Class;
/**
* se::Object represents JavaScript Object.
*/
class Object final : public RefCounter
{
public:
/**
* @brief Creates a JavaScript Object like `{} or new Object()`.
* @return A JavaScript Object, or nullptr if there is an error.
* @note The return value (non-null) has to be released manually.
*/
static Object* createPlainObject();
/**
* @brief Creates a JavaScript Array Object like `[] or new Array()`.
* @param[in] length The initical length of array.
* @return A JavaScript Array Object, or nullptr if there is an error.
* @note The return value (non-null) has to be released manually.
*/
static Object* createArrayObject(size_t length);
/**
* @brief Creates a JavaScript Typed Array Object with uint8 format from an existing pointer.
* @param[in] bytes A pointer to the byte buffer to be used as the backing store of the Typed Array object.
* @param[in] byteLength The number of bytes pointed to by the parameter bytes.
* @return A JavaScript Typed Array Object whose backing store is the same as the one pointed data, or nullptr if there is an error.
* @note The return value (non-null) has to be released manually.
* @deprecated This method is deprecated, please use `se::Object::createTypedArray` instead.
*/
SE_DEPRECATED_ATTRIBUTE static Object* createUint8TypedArray(uint8_t* bytes, size_t byteLength);
enum class TypedArrayType
{
NONE,
INT8,
INT16,
INT32,
UINT8,
UINT8_CLAMPED,
UINT16,
UINT32,
FLOAT32,
FLOAT64
};
/**
* @brief Creates a JavaScript Typed Array Object with specified format from an existing pointer,
if provide a null pointer,then will create a empty JavaScript Typed Array Object.
* @param[in] type The format of typed array.
* @param[in] data A pointer to the byte buffer to be used as the backing store of the Typed Array object.
* @param[in] byteLength The number of bytes pointed to by the parameter bytes.
* @return A JavaScript Typed Array Object whose backing store is the same as the one pointed data, or nullptr if there is an error.
* @note The return value (non-null) has to be released manually.
*/
static Object* createTypedArray(TypedArrayType type, void* data, size_t byteLength);
/**
* @brief Creates a JavaScript Array Buffer object from an existing pointer.
* @param[in] bytes A pointer to the byte buffer to be used as the backing store of the Typed Array object.
* @param[in] byteLength The number of bytes pointed to by the parameter bytes.
* @return A Array Buffer Object whose backing store is the same as the one pointed to data, or nullptr if there is an error.
* @note The return value (non-null) has to be released manually.
*/
static Object* createArrayBufferObject(void* bytes, size_t byteLength);
/**
* @brief Creates a JavaScript Object from a JSON formatted string.
* @param[in] jsonStr The utf-8 string containing the JSON string to be parsed.
* @return A JavaScript Object containing the parsed value, or nullptr if the input is invalid.
* @note The return value (non-null) has to be released manually.
*/
static Object* createJSONObject(const std::string& jsonStr);
/**
* @brief Creates a JavaScript Native Binding Object from an existing se::Class instance.
* @param[in] cls The se::Class instance which stores native callback informations.
* @return A JavaScript Native Binding Object, or nullptr if there is an error.
* @note The return value (non-null) has to be released manually.
*/
static Object* createObjectWithClass(Class* cls);
/**
* @brief Gets a se::Object from an existing native object pointer.
* @param[in] ptr The native object pointer associated with the se::Object
* @return A JavaScript Native Binding Object, or nullptr if there is an error.
* @note The return value (non-null) has to be released manually.
*/
static Object* getObjectWithPtr(void* ptr);
/**
* @brief Gets a property from an object.
* @param[in] name A utf-8 string containing the property's name.
* @param[out] value The property's value if object has the property, otherwise the undefined value.
* @return true if object has the property, otherwise false.
*/
bool getProperty(const char* name, Value* value);
/**
* @brief Sets a property to an object.
* @param[in] name A utf-8 string containing the property's name.
* @param[in] value A value to be used as the property's value.
* @return true if the property is set successfully, otherwise false.
*/
bool setProperty(const char* name, const Value& value);
/**
* @brief Delete a property of an object.
* @param[in] name A utf-8 string containing the property's name.
* @return true if the property is deleted successfully, otherwise false.
*/
bool deleteProperty(const char *name);
/**
* @brief Defines a property with native accessor callbacks for an object.
* @param[in] name A utf-8 string containing the property's name.
* @param[in] getter The native callback for getter.
* @param[in] setter The native callback for setter.
* @return true if succeed, otherwise false.
*/
bool defineProperty(const char *name, JSObjectCallAsFunctionCallback getter, JSObjectCallAsFunctionCallback setter);
/**
* @brief Defines a function with a native callback for an object.
* @param[in] funcName A utf-8 string containing the function name.
* @param[in] func The native callback triggered by JavaScript code.
* @return true if succeed, otherwise false.
*/
bool defineFunction(const char *funcName, JSObjectCallAsFunctionCallback func);
/**
* @brief Tests whether an object can be called as a function.
* @return true if object can be called as a function, otherwise false.
*/
bool isFunction() const;
/**
* @brief Calls an object as a function.
* @param[in] args A se::Value array of arguments to pass to the function. Pass se::EmptyValueArray if argumentCount is 0.
* @param[in] thisObject The object to use as "this," or NULL to use the global object as "this."
* @param[out] rval The se::Value that results from calling object as a function, passing nullptr if return value is ignored.
* @return true if object is a function and there isn't any errors, otherwise false.
*/
bool call(const ValueArray& args, Object* thisObject, Value* rval = nullptr);
/**
* @brief Tests whether an object is an array.
* @return true if object is an array, otherwise false.
*/
bool isArray() const;
/**
* @brief Gets array length of an array object.
* @param[out] length The array length to be stored. It's set to 0 if there is an error.
* @return true if succeed, otherwise false.
*/
bool getArrayLength(uint32_t* length) const;
/**
* @brief Gets an element from an array object by numeric index.
* @param[in] index An integer value for index.
* @param[out] data The se::Value to be stored for the element in certain index.
* @return true if succeed, otherwise false.
*/
bool getArrayElement(uint32_t index, Value* data) const;
/**
* @brief Sets an element to an array object by numeric index.
* @param[in] index An integer value for index.
* @param[in] data The se::Value to be set to array with certain index.
* @return true if succeed, otherwise false.
*/
bool setArrayElement(uint32_t index, const Value& data);
/** @brief Tests whether an object is a typed array.
* @return true if object is a typed array, otherwise false.
*/
bool isTypedArray() const;
/**
* @brief Gets the type of a typed array object.
* @return The type of a typed array object.
*/
TypedArrayType getTypedArrayType() const;
/**
* @brief Gets backing store of a typed array object.
* @param[out] ptr A temporary pointer to the backing store of a JavaScript Typed Array object.
* @param[out] length The byte length of a JavaScript Typed Array object.
* @return true if succeed, otherwise false.
*/
bool getTypedArrayData(uint8_t** ptr, size_t* length) const;
/**
* @brief Tests whether an object is an array buffer object.
* @return true if object is an array buffer object, otherwise false.
*/
bool isArrayBuffer() const;
/**
* @brief Gets buffer data of an array buffer object.
* @param[out] ptr A pointer to the data buffer that serves as the backing store for a JavaScript Typed Array object.
* @param[out] length The number of bytes in a JavaScript data object.
* @return true if succeed, otherwise false.
*/
bool getArrayBufferData(uint8_t** ptr, size_t* length) const;
/**
* @brief Gets all property names of an object.
* @param[out] allKeys A string vector to store all property names.
* @return true if succeed, otherwise false.
*/
bool getAllKeys(std::vector<std::string>* allKeys) const;
/**
* @brief Sets a pointer to private data on an object.
* @param[in] data A void* to set as the object's private data.
* @note This method will associate private data with se::Object by std::unordered_map::emplace.
* It's used for search a se::Object via a void* private data.
*/
void setPrivateData(void* data);
/**
* @brief Gets an object's private data.
* @return A void* that is the object's private data, if the object has private data, otherwise nullptr.
*/
void* getPrivateData() const;
/**
* @brief Clears private data of an object.
* @param clearMapping Whether to clear the mapping of native object & se::Object.
*/
void clearPrivateData(bool clearMapping = true);
/**
* @brief Roots an object from garbage collection.
* @note Use this method when you want to store an object in a global or on the heap, where the garbage collector will not be able to discover your reference to it.
* An object may be rooted multiple times and must be unrooted an equal number of times before becoming eligible for garbage collection.
*/
void root();
/**
* @brief Unroots an object from garbage collection.
* @note An object may be rooted multiple times and must be unrooted an equal number of times before becoming eligible for garbage collection.
*/
void unroot();
/**
* @brief Tests whether an object is rooted.
* @return true if it has been already rooted, otherwise false.
*/
bool isRooted() const;
/**
* @brief Tests whether two objects are strict equal, as compared by the JS === operator.
* @param[in] o The object to be tested with this object.
* @return true if the two values are strict equal, otherwise false.
*/
bool strictEquals(Object* o) const;
/**
* @brief Attaches an object to current object.
* @param[in] obj The object to be attached.
* @return true if succeed, otherwise false.
* @note This method will set `obj` as a property of current object, therefore the lifecycle of child object will depend on current object,
* which means `obj` may be garbage collected only after current object is garbage collected.
* It's normally used in binding a native callback method. For example:
```javascript
var self = this;
someObject.setCallback(function(){}, self);
```
```c++
static bool SomeObject_setCallback(se::State& s)
{
SomeObject* cobj = (SomeObject*)s.nativeThisObject();
const auto& args = s.args();
size_t argc = args.size();
if (argc == 2) {
std::function<void()> arg0;
do {
if (args[0].isObject() && args[0].toObject()->isFunction())
{
se::Value jsThis(args[1]);
se::Value jsFunc(args[0]);
jsThis.toObject()->attachObject(jsFunc.toObject());
auto lambda = [=]() -> void {
...
// Call jsFunc stuff...
...
};
arg0 = lambda;
}
else
{
arg0 = nullptr;
}
} while(false);
SE_PRECONDITION2(ok, false, "Error processing arguments");
cobj->setCallback(arg0);
return true;
}
return false;
}
```
*/
bool attachObject(Object* obj);
/**
* @brief Detaches an object from current object.
* @param[in] obj The object to be detached.
* @return true if succeed, otherwise false.
* @note The attached object will not be released if current object is not garbage collected.
*/
bool detachObject(Object* obj);
/**
* @brief Returns the string for describing current object.
* @return The string for describing current object.
*/
std::string toString() const;
// Private API used in wrapper
static Object* _createJSObject(Class* cls, JSObjectRef obj);
JSObjectRef _getJSObject() const;
Class* _getClass() const;
void _setFinalizeCallback(JSObjectFinalizeCallback finalizeCb);
void _cleanup(void* nativeObject = nullptr);
bool _isNativeFunction() const;
//
private:
static void setContext(JSContextRef cx);
static void cleanup();
Object();
virtual ~Object();
bool init(Class* cls, JSObjectRef obj);
enum class Type : char
{
UNKNOWN,
PLAIN,
ARRAY,
ARRAY_BUFFER,
TYPED_ARRAY_INT8,
TYPED_ARRAY_INT16,
TYPED_ARRAY_INT32,
TYPED_ARRAY_UINT8,
TYPED_ARRAY_UINT8_CLAMPED,
TYPED_ARRAY_UINT16,
TYPED_ARRAY_UINT32,
TYPED_ARRAY_FLOAT32,
TYPED_ARRAY_FLOAT64,
FUNCTION
};
Class* _cls;
JSObjectRef _obj;
void* _privateData;
JSObjectFinalizeCallback _finalizeCb;
uint32_t _rootCount;
uint32_t _currentVMId;
#if SE_DEBUG > 0
public:
uint32_t _id;
private:
#endif
bool _isCleanup;
mutable Type _type;
friend class ScriptEngine;
friend class AutoHandleScope;
};
} // namespace se {
#endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_JSC

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,32 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
namespace se {
bool isSupportTypedArrayAPI();
bool isSupportArrayTestAPI();
} // namespace se

View File

@@ -0,0 +1,81 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#include "PlatformUtils.h"
#include "../config.hpp"
#if defined(__APPLE__)
#include <TargetConditionals.h>
#endif
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#elif TARGET_OS_MAC
#import <Foundation/Foundation.h>
#endif
namespace se {
bool isSupportTypedArrayAPI()
{
static bool isSupported = false;
static bool isInited = false;
if (!isInited)
{
#if TARGET_OS_IPHONE
float version = [[UIDevice currentDevice].systemVersion floatValue];
isSupported = (version >= 10.0f);
#elif TARGET_OS_MAC
NSOperatingSystemVersion minimumSupportedOSVersion = { .majorVersion = 10, .minorVersion = 12, .patchVersion = 0 };
isSupported = [NSProcessInfo.processInfo isOperatingSystemAtLeastVersion:minimumSupportedOSVersion] ? true : false;
#else
SE_LOGE("isSupportTypedArrayAPI: Unknown system!");
#endif
isInited = true;
}
return isSupported;
}
bool isSupportArrayTestAPI()
{
static bool isSupported = false;
static bool isInited = false;
if (!isInited)
{
#if TARGET_OS_IPHONE
float version = [[UIDevice currentDevice].systemVersion floatValue];
isSupported = (version >= 9.0f);
#elif TARGET_OS_MAC
NSOperatingSystemVersion minimumSupportedOSVersion = { .majorVersion = 10, .minorVersion = 11, .patchVersion = 0 };
isSupported = [NSProcessInfo.processInfo isOperatingSystemAtLeastVersion:minimumSupportedOSVersion] ? true : false;
#else
SE_LOGE("isSupportArrayTestAPI: Unknown system!");
#endif
isInited = true;
}
return isSupported;
}
} // namespace se

View File

@@ -0,0 +1,331 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
#include "../config.hpp"
#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_JSC
#include "Base.h"
#include <thread>
namespace se {
class Object;
class Class;
class Value;
extern Class* __jsb_CCPrivateData_class;
/**
* A stack-allocated class that governs a number of local handles.
* It's only implemented for v8 wrapper now.
* Other script engine wrappers have empty implementation for this class.
* It's used at the beginning of executing any wrapper API.
*/
class AutoHandleScope
{
public:
AutoHandleScope();
~AutoHandleScope();
};
/**
* ScriptEngine is a sington which represents a context of JavaScript VM.
*/
class ScriptEngine final
{
public:
/**
* @brief Gets or creates the instance of script engine.
* @return The script engine instance.
*/
static ScriptEngine* getInstance();
/**
* @brief Destroys the instance of script engine.
*/
static void destroyInstance();
/**
* @brief Gets the global object of JavaScript VM.
* @return The se::Object stores the global JavaScript object.
*/
Object* getGlobalObject();
typedef bool (*RegisterCallback)(Object*);
/**
* @brief Adds a callback for registering a native binding module.
* @param[in] cb A callback for registering a native binding module.
* @note This method just add a callback to a vector, callbacks is invoked in `start` method.
*/
void addRegisterCallback(RegisterCallback cb);
/**
* @brief Starts the script engine.
* @return true if succeed, otherwise false.
* @note This method will invoke all callbacks of native binding modules by the order of registration.
*/
bool start();
/**
* @brief Initializes script engine.
* @return true if succeed, otherwise false.
* @note This method will create JavaScript context and global object.
*/
bool init();
/**
* @brief Adds a hook function before initializing script engine.
* @param[in] hook A hook function to be invoked before initializing script engine.
* @note Multiple hook functions could be added, they will be invoked by the order of adding.
*/
void addBeforeInitHook(const std::function<void()>& hook);
/**
* @brief Adds a hook function after initializing script engine.
* @param[in] hook A hook function to be invoked before initializing script engine.
* @note Multiple hook functions could be added, they will be invoked by the order of adding.
*/
void addAfterInitHook(const std::function<void()>& hook);
/**
* @brief Cleanups script engine.
* @note This method will removes all objects in JavaScript VM even whose are rooted, then shutdown JavaScript VMf.
*/
void cleanup();
/**
* @brief Adds a hook function before cleanuping script engine.
* @param[in] hook A hook function to be invoked before cleanuping script engine.
* @note Multiple hook functions could be added, they will be invoked by the order of adding.
*/
void addBeforeCleanupHook(const std::function<void()>& hook);
/**
* @brief Adds a hook function after cleanuping script engine.
* @param[in] hook A hook function to be invoked after cleanuping script engine.
* @note Multiple hook functions could be added, they will be invoked by the order of adding.
*/
void addAfterCleanupHook(const std::function<void()>& hook);
/**
* @brief Executes a utf-8 string buffer which contains JavaScript code.
* @param[in] scriptStr A utf-8 string buffer, if it isn't null-terminated, parameter `length` should be assigned and > 0.
* @param[in] length The length of parameter `scriptStr`, it will be set to string length internally if passing < 0 and parameter `scriptStr` is null-terminated.
* @param[in] rval The se::Value that results from evaluating script. Passing nullptr if you don't care about the result.
* @param[in] fileName A string containing a URL for the script's source file. This is used by debuggers and when reporting exceptions. Pass NULL if you do not care to include source file information.
* @return true if succeed, otherwise false.
*/
bool evalString(const char* scriptStr, ssize_t length = -1, Value* rval = nullptr, const char* fileName = nullptr);
/**
* Delegate class for file operation
*/
class FileOperationDelegate
{
public:
FileOperationDelegate()
: onGetDataFromFile(nullptr)
, onGetStringFromFile(nullptr)
, onCheckFileExist(nullptr)
, onGetFullPath(nullptr)
{}
/**
* @brief Tests whether delegate is valid.
*/
bool isValid() const {
return onGetDataFromFile != nullptr
&& onGetStringFromFile != nullptr
&& onCheckFileExist != nullptr
&& onGetFullPath != nullptr; }
// path, buffer, buffer size
std::function<void(const std::string&, const std::function<void(const uint8_t*, size_t)>& )> onGetDataFromFile;
// path, return file string content.
std::function<std::string(const std::string&)> onGetStringFromFile;
// path
std::function<bool(const std::string&)> onCheckFileExist;
// path, return full path
std::function<std::string(const std::string&)> onGetFullPath;
};
/**
* @brief Sets the delegate for file operation.
* @param delegate[in] The delegate instance for file operation.
*/
void setFileOperationDelegate(const FileOperationDelegate& delegate);
/**
* @brief Gets the delegate for file operation.
* @return The delegate for file operation
*/
const FileOperationDelegate& getFileOperationDelegate() const;
/**
* @brief Executes a file which contains JavaScript code.
* @param[in] path Script file path.
* @param[in] rval The se::Value that results from evaluating script. Passing nullptr if you don't care about the result.
* @return true if succeed, otherwise false.
*/
bool runScript(const std::string& path, Value* rval = nullptr);
/**
* @brief Tests whether script engine is doing garbage collection.
* @return true if it's in garbage collection, otherwise false.
*/
bool isGarbageCollecting();
/**
* @brief Performs a JavaScript garbage collection.
*/
void garbageCollect();
/**
* @brief Tests whether script engine is being cleaned up.
* @return true if it's in cleaning up, otherwise false.
*/
bool isInCleanup() { return _isInCleanup; }
/**
* @brief Tests whether script engine is valid.
* @return true if it's valid, otherwise false.
*/
bool isValid() { return _isValid; }
/**
* @brief Clears all exceptions.
*/
void clearException();
using ExceptionCallback = std::function<void(const char*, const char*, const char*)>; // location, message, stack
/**
* @brief Sets the callback function while an exception is fired.
* @param[in] cb The callback function to notify that an exception is fired.
*/
void setExceptionCallback(const ExceptionCallback& cb);
/**
* @brief Sets the callback function while an exception is fired in JS.
* @param[in] cb The callback function to notify that an exception is fired.
*/
void setJSExceptionCallback(const ExceptionCallback& cb);
/**
* @brief Gets the start time of script engine.
* @return The start time of script engine.
*/
const std::chrono::steady_clock::time_point& getStartTime() const { return _startTime; }
/**
* @brief Enables JavaScript debugger
* @param[in] serverAddr The address of debugger server.
* @param[in] port The port of debugger server will use.
* @param[in] isWait Whether wait debugger attach when loading.
*/
void enableDebugger(const std::string& serverAddr, uint32_t port, bool isWait = false);
/**
* @brief Tests whether JavaScript debugger is enabled
* @return true if JavaScript debugger is enabled, otherwise false.
*/
bool isDebuggerEnabled() const;
/**
* @brief Main loop update trigger, it's need to invoked in main thread every frame.
*/
void mainLoopUpdate();
/**
* @brief Gets script virtual machine instance ID. Default value is 1, increase by 1 if `init` is invoked.
*/
uint32_t getVMId() const { return _vmId; }
// Private API used in wrapper
void _clearException(JSValueRef exception);
JSContextRef _getContext() const { return _cx; }
void _setGarbageCollecting(bool isGarbageCollecting);
//
private:
ScriptEngine();
~ScriptEngine();
struct ExceptionInfo
{
std::string location;
std::string message;
std::string stack;
// For compatibility
std::string filePath;
uint32_t lineno;
ExceptionInfo()
: lineno(0)
{}
bool isValid() const
{
return !message.empty();
}
};
ExceptionInfo _formatException(JSValueRef exception);
void callExceptionCallback(const char*, const char*, const char*);
std::chrono::steady_clock::time_point _startTime;
std::vector<RegisterCallback> _registerCallbackArray;
std::vector<std::function<void()>> _beforeInitHookArray;
std::vector<std::function<void()>> _afterInitHookArray;
std::vector<std::function<void()>> _beforeCleanupHookArray;
std::vector<std::function<void()>> _afterCleanupHookArray;
JSGlobalContextRef _cx;
Object* _globalObj;
FileOperationDelegate _fileOperationDelegate;
ExceptionCallback _nativeExceptionCallback = nullptr;
ExceptionCallback _jsExceptionCallback = nullptr;
uint32_t _vmId;
std::thread::id _engineThreadId;
bool _isGarbageCollecting;
bool _isValid;
bool _isInCleanup;
bool _isErrorHandleWorking;
bool _isDebuggerEnabled;
};
} // namespace se {
#endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_JSC

View File

@@ -0,0 +1,694 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#include "ScriptEngine.hpp"
#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_JSC
#include "Object.hpp"
#include "Class.hpp"
#include "Utils.hpp"
#include "../State.hpp"
#include "../MappingUtils.hpp"
#include "PlatformUtils.h"
#import "EJConvertTypedArray.h"
#if SE_DEBUG > 0
extern "C" JS_EXPORT void JSSynchronousGarbageCollectForDebugging(JSContextRef);
#endif
namespace se {
AutoHandleScope::AutoHandleScope()
{
}
AutoHandleScope::~AutoHandleScope()
{
}
Class* __jsb_CCPrivateData_class = nullptr;
//
namespace {
ScriptEngine* __instance = nullptr;
JSValueRef __forceGC(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject,
size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
{
if (__instance != nullptr)
{
__instance->garbageCollect();
}
return JSValueMakeUndefined(ctx);
}
JSValueRef __log(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject,
size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
{
if (argumentCount > 0)
{
std::string ret;
internal::forceConvertJsValueToStdString(ctx, arguments[0], &ret);
SE_LOGD("JS: %s\n", ret.c_str());
}
return JSValueMakeUndefined(ctx);
}
JSObjectRef privateDataContructor(JSContextRef ctx, JSObjectRef constructor, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
{
return nullptr;
}
void privateDataFinalize(JSObjectRef obj)
{
internal::PrivateData* p = (internal::PrivateData*)JSObjectGetPrivate(obj);
JSObjectSetPrivate(obj, p->data);
if (p->finalizeCb != nullptr)
p->finalizeCb(obj);
free(p);
}
Value __consoleVal;
Value __oldConsoleLog;
Value __oldConsoleDebug;
Value __oldConsoleInfo;
Value __oldConsoleWarn;
Value __oldConsoleError;
Value __oldConsoleAssert;
bool JSB_console_format_log(State& s, const char* prefix, int msgIndex = 0)
{
if (msgIndex < 0)
return false;
const auto& args = s.args();
int argc = (int)args.size();
if ((argc - msgIndex) == 1)
{
std::string msg = args[msgIndex].toStringForce();
SE_LOGD("JS: %s%s\n", prefix, msg.c_str());
}
else if (argc > 1)
{
std::string msg = args[msgIndex].toStringForce();
size_t pos;
for (int i = (msgIndex+1); i < argc; ++i)
{
pos = msg.find("%");
if (pos != std::string::npos && pos != (msg.length()-1) && (msg[pos+1] == 'd' || msg[pos+1] == 's' || msg[pos+1] == 'f'))
{
msg.replace(pos, 2, args[i].toStringForce());
}
else
{
msg += " " + args[i].toStringForce();
}
}
SE_LOGD("JS: %s%s\n", prefix, msg.c_str());
}
return true;
}
// exist potential crash in iOS, when invoke log to javascript
void JSB_invoke_js_log(Value& v, State& s)
{
#if SE_LOG_TO_JS_ENV
v.toObject()->call(s.args(), __consoleVal.toObject());
#endif
}
bool JSB_console_log(State& s)
{
JSB_console_format_log(s, "");
JSB_invoke_js_log(__oldConsoleLog, s);
return true;
}
SE_BIND_FUNC(JSB_console_log)
bool JSB_console_debug(State& s)
{
JSB_console_format_log(s, "[DEBUG]: ");
JSB_invoke_js_log(__oldConsoleDebug, s);
return true;
}
SE_BIND_FUNC(JSB_console_debug)
bool JSB_console_info(State& s)
{
JSB_console_format_log(s, "[INFO]: ");
JSB_invoke_js_log(__oldConsoleInfo, s);
return true;
}
SE_BIND_FUNC(JSB_console_info)
bool JSB_console_warn(State& s)
{
JSB_console_format_log(s, "[WARN]: ");
JSB_invoke_js_log(__oldConsoleWarn, s);
return true;
}
SE_BIND_FUNC(JSB_console_warn)
bool JSB_console_error(State& s)
{
JSB_console_format_log(s, "[ERROR]: ");
JSB_invoke_js_log(__oldConsoleError, s);
return true;
}
SE_BIND_FUNC(JSB_console_error)
bool JSB_console_assert(State& s)
{
const auto& args = s.args();
if (!args.empty())
{
if (args[0].isBoolean() && !args[0].toBoolean())
{
JSB_console_format_log(s, "[ASSERT]: ", 1);
JSB_invoke_js_log(__oldConsoleAssert, s);
}
}
return true;
}
SE_BIND_FUNC(JSB_console_assert)
}
ScriptEngine *ScriptEngine::getInstance()
{
if (__instance == nullptr)
{
__instance = new ScriptEngine();
}
return __instance;
}
void ScriptEngine::destroyInstance()
{
delete __instance;
__instance = nullptr;
}
ScriptEngine::ScriptEngine()
: _cx(nullptr)
, _globalObj(nullptr)
, _vmId(0)
, _isGarbageCollecting(false)
, _isValid(false)
, _isInCleanup(false)
, _isErrorHandleWorking(false)
, _isDebuggerEnabled(false)
{
}
bool ScriptEngine::init()
{
cleanup();
SE_LOGD("Initializing JavaScriptCore \n");
++_vmId;
_engineThreadId = std::this_thread::get_id();
for (const auto& hook : _beforeInitHookArray)
{
hook();
}
_beforeInitHookArray.clear();
_cx = JSGlobalContextCreate(nullptr);
if (nullptr == _cx)
return false;
JSStringRef ctxName = JSStringCreateWithUTF8CString("Cocos2d-x JSB");
JSGlobalContextSetName(_cx, ctxName);
JSStringRelease(ctxName);
NativePtrToObjectMap::init();
NonRefNativePtrCreatedByCtorMap::init();
internal::setContext(_cx);
Class::setContext(_cx);
Object::setContext(_cx);
JSObjectRef globalObj = JSContextGetGlobalObject(_cx);
if (nullptr == globalObj)
return false;
_globalObj = Object::_createJSObject(nullptr, globalObj);
_globalObj->root();
_globalObj->setProperty("window", Value(_globalObj));
if (!isSupportTypedArrayAPI())
EJJSContextPrepareTypedArrayAPI(_cx);
if (_globalObj->getProperty("console", &__consoleVal) && __consoleVal.isObject())
{
#if SE_LOG_TO_JS_ENV
__consoleVal.toObject()->getProperty("log", &__oldConsoleLog);
__consoleVal.toObject()->getProperty("debug", &__oldConsoleDebug);
__consoleVal.toObject()->getProperty("info", &__oldConsoleInfo);
__consoleVal.toObject()->getProperty("warn", &__oldConsoleWarn);
__consoleVal.toObject()->getProperty("error", &__oldConsoleError);
__consoleVal.toObject()->getProperty("assert", &__oldConsoleAssert);
#endif
__consoleVal.toObject()->defineFunction("log", _SE(JSB_console_log));
__consoleVal.toObject()->defineFunction("debug", _SE(JSB_console_debug));
__consoleVal.toObject()->defineFunction("info", _SE(JSB_console_info));
__consoleVal.toObject()->defineFunction("warn", _SE(JSB_console_warn));
__consoleVal.toObject()->defineFunction("error", _SE(JSB_console_error));
__consoleVal.toObject()->defineFunction("assert", _SE(JSB_console_assert));
}
_globalObj->setProperty("scriptEngineType", Value("JavaScriptCore"));
JSStringRef propertyName = JSStringCreateWithUTF8CString("log");
JSObjectSetProperty(_cx, globalObj, propertyName, JSObjectMakeFunctionWithCallback(_cx, propertyName, __log), kJSPropertyAttributeReadOnly, nullptr);
JSStringRelease(propertyName);
propertyName = JSStringCreateWithUTF8CString("forceGC");
JSObjectSetProperty(_cx, globalObj, propertyName, JSObjectMakeFunctionWithCallback(_cx, propertyName, __forceGC), kJSPropertyAttributeReadOnly, nullptr);
JSStringRelease(propertyName);
__jsb_CCPrivateData_class = Class::create("__PrivateData", _globalObj, nullptr, privateDataContructor);
__jsb_CCPrivateData_class->defineFinalizeFunction(privateDataFinalize);
__jsb_CCPrivateData_class->install();
_isValid = true;
for (const auto& hook : _afterInitHookArray)
{
hook();
}
_afterInitHookArray.clear();
return true;
}
ScriptEngine::~ScriptEngine()
{
cleanup();
}
void ScriptEngine::cleanup()
{
if (!_isValid)
return;
SE_LOGD("ScriptEngine::cleanup begin ...\n");
_isInCleanup = true;
for (const auto& hook : _beforeCleanupHookArray)
{
hook();
}
_beforeCleanupHookArray.clear();
SAFE_DEC_REF(_globalObj);
Object::cleanup();
garbageCollect();
__consoleVal.setUndefined();
__oldConsoleLog.setUndefined();
__oldConsoleDebug.setUndefined();
__oldConsoleInfo.setUndefined();
__oldConsoleWarn.setUndefined();
__oldConsoleError.setUndefined();
__oldConsoleAssert.setUndefined();
JSGlobalContextRelease(_cx);
Class::cleanup();
_cx = nullptr;
_globalObj = nullptr;
_isValid = false;
_registerCallbackArray.clear();
for (const auto& hook : _afterCleanupHookArray)
{
hook();
}
_afterCleanupHookArray.clear();
_isInCleanup = false;
NativePtrToObjectMap::destroy();
NonRefNativePtrCreatedByCtorMap::destroy();
SE_LOGD("ScriptEngine::cleanup end ...\n");
}
ScriptEngine::ExceptionInfo ScriptEngine::_formatException(JSValueRef exception)
{
ExceptionInfo ret;
// Ignore Exception in forceConvertJsValueToStdString invocation to avoid infinite loop.
internal::forceConvertJsValueToStdString(_cx, exception, &ret.message, true);
JSType type = JSValueGetType(_cx, exception);
if (type == kJSTypeObject)
{
JSObjectRef obj = JSValueToObject(_cx, exception, nullptr);
JSStringRef stackKey = JSStringCreateWithUTF8CString("stack");
JSValueRef stackVal = JSObjectGetProperty(_cx, obj, stackKey, nullptr);
if (stackKey != nullptr)
{
type = JSValueGetType(_cx, stackVal);
if (type == kJSTypeString)
{
JSStringRef stackStr = JSValueToStringCopy(_cx, stackVal, nullptr);
internal::jsStringToStdString(_cx, stackStr, &ret.stack);
JSStringRelease(stackStr);
}
JSStringRelease(stackKey);
}
std::string line;
std::string column;
std::string filePath;
JSPropertyNameArrayRef nameArr = JSObjectCopyPropertyNames(_cx, obj);
size_t count =JSPropertyNameArrayGetCount(nameArr);
for (size_t i = 0; i < count; ++i)
{
JSStringRef jsName = JSPropertyNameArrayGetNameAtIndex(nameArr, i);
JSValueRef jsValue = JSObjectGetProperty(_cx, obj, jsName, nullptr);
std::string name;
internal::jsStringToStdString(_cx, jsName, &name);
std::string value;
JSStringRef jsstr = JSValueToStringCopy(_cx, jsValue, nullptr);
internal::jsStringToStdString(_cx, jsstr, &value);
JSStringRelease(jsstr);
if (name == "line")
{
line = value;
ret.lineno = (uint32_t)JSValueToNumber(_cx, jsValue, nullptr);
}
else if (name == "column")
{
column = value;
}
else if (name == "sourceURL")
{
filePath = value;
ret.filePath = value;
}
}
ret.location = filePath + ":" + line + ":" + column;
JSPropertyNameArrayRelease(nameArr);
}
return ret;
}
void ScriptEngine::_clearException(JSValueRef exception)
{
if (exception != nullptr)
{
ExceptionInfo exceptionInfo = _formatException(exception);
if (exceptionInfo.isValid())
{
std::string exceptionStr = exceptionInfo.message;
exceptionStr += ", location: " + exceptionInfo.location;
if (!exceptionInfo.stack.empty())
{
exceptionStr += "\nSTACK:\n" + exceptionInfo.stack;
}
SE_LOGE("ERROR: %s\n", exceptionStr.c_str());
callExceptionCallback(exceptionInfo.location.c_str(), exceptionInfo.message.c_str(), exceptionInfo.stack.c_str());
if (!_isErrorHandleWorking)
{
_isErrorHandleWorking = true;
Value errorHandler;
if (_globalObj->getProperty("__errorHandler", &errorHandler) && errorHandler.isObject() && errorHandler.toObject()->isFunction())
{
ValueArray args;
args.push_back(Value(exceptionInfo.filePath));
args.push_back(Value(exceptionInfo.lineno));
args.push_back(Value(exceptionInfo.message));
args.push_back(Value(exceptionInfo.stack));
errorHandler.toObject()->call(args, _globalObj);
}
_isErrorHandleWorking = false;
}
else
{
SE_LOGE("ERROR: __errorHandler has exception\n");
}
}
}
}
void ScriptEngine::callExceptionCallback(const char * location, const char * message, const char * stack)
{
if(_nativeExceptionCallback) {
_nativeExceptionCallback(location, message, stack);
}
if(_jsExceptionCallback) {
_jsExceptionCallback(location, message, stack);
}
}
void ScriptEngine::setExceptionCallback(const ExceptionCallback& cb)
{
_nativeExceptionCallback = cb;
}
void ScriptEngine::setJSExceptionCallback(const ExceptionCallback& cb)
{
_jsExceptionCallback = cb;
}
bool ScriptEngine::isGarbageCollecting()
{
return _isGarbageCollecting;
}
void ScriptEngine::_setGarbageCollecting(bool isGarbageCollecting)
{
_isGarbageCollecting = isGarbageCollecting;
}
Object* ScriptEngine::getGlobalObject()
{
return _globalObj;
}
void ScriptEngine::addBeforeInitHook(const std::function<void()>& hook)
{
_beforeInitHookArray.push_back(hook);
}
void ScriptEngine::addAfterInitHook(const std::function<void()>& hook)
{
_afterInitHookArray.push_back(hook);
}
void ScriptEngine::addBeforeCleanupHook(const std::function<void()>& hook)
{
_beforeCleanupHookArray.push_back(hook);
}
void ScriptEngine::addAfterCleanupHook(const std::function<void()>& hook)
{
_afterCleanupHookArray.push_back(hook);
}
void ScriptEngine::addRegisterCallback(RegisterCallback cb)
{
assert(std::find(_registerCallbackArray.begin(), _registerCallbackArray.end(), cb) == _registerCallbackArray.end());
_registerCallbackArray.push_back(cb);
}
bool ScriptEngine::start()
{
if (!init())
return false;
bool ok = false;
_startTime = std::chrono::steady_clock::now();
for (auto cb : _registerCallbackArray)
{
ok = cb(_globalObj);
assert(ok);
if (!ok)
break;
}
// After ScriptEngine is started, _registerCallbackArray isn't needed. Therefore, clear it here.
_registerCallbackArray.clear();
return ok;
}
void ScriptEngine::garbageCollect()
{
// JSSynchronousGarbageCollectForDebugging is private API in JavaScriptCore framework.
// AppStore will reject the apps reference non-public symbols.
// Therefore, we use JSSynchronousGarbageCollectForDebugging for easily debugging memory
// problems in debug mode, and use public API JSGarbageCollect in release mode to pass
// the AppStore's verify check.
#if SE_DEBUG > 0
SE_LOGD("GC begin ..., (Native -> JS map) count: %d\n", (int)NativePtrToObjectMap::size());
JSSynchronousGarbageCollectForDebugging(_cx);
SE_LOGD("GC end ..., (Native -> JS map) count: %d\n", (int)NativePtrToObjectMap::size());
#else
JSGarbageCollect(_cx);
#endif
}
bool ScriptEngine::evalString(const char* script, ssize_t length/* = -1 */, Value* ret/* = nullptr */, const char* fileName/* = nullptr */)
{
if(_engineThreadId != std::this_thread::get_id())
{
// `evalString` should run in main thread
assert(false);
return false;
}
assert(script != nullptr);
if (length < 0)
length = strlen(script);
if (fileName == nullptr)
fileName = "(no filename)";
// Fix the source url is too long displayed in Safari debugger.
std::string sourceUrl = fileName;
static const std::string prefixKey = "/temp/quick-scripts/";
size_t prefixPos = sourceUrl.find(prefixKey);
if (prefixPos != std::string::npos)
{
sourceUrl = sourceUrl.substr(prefixPos + prefixKey.length());
}
std::string exceptionStr;
std::string scriptStr(script, length);
JSValueRef exception = nullptr;
JSStringRef jsSourceUrl = JSStringCreateWithUTF8CString(sourceUrl.c_str());
JSStringRef jsScript = JSStringCreateWithUTF8CString(scriptStr.c_str());
JSValueRef result = nullptr;
bool ok = JSCheckScriptSyntax(_cx, jsScript, jsSourceUrl, 1, &exception);;
if (ok)
{
result = JSEvaluateScript(_cx, jsScript, nullptr, jsSourceUrl, 1, &exception);
if (exception != nullptr)
{
ok = false;
}
}
else
{
if (exception == nullptr)
{
SE_LOGD("Unknown syntax error parsing file %s\n", fileName);
}
}
JSStringRelease(jsScript);
JSStringRelease(jsSourceUrl);
if (ok)
{
if (ret != nullptr)
internal::jsToSeValue(_cx, result, ret);
}
if (!ok)
{
SE_LOGE("ScriptEngine::evalString script %s, failed!\n", fileName);
}
_clearException(exception);
return ok;
}
void ScriptEngine::setFileOperationDelegate(const FileOperationDelegate& delegate)
{
_fileOperationDelegate = delegate;
}
const ScriptEngine::FileOperationDelegate& ScriptEngine::getFileOperationDelegate() const
{
return _fileOperationDelegate;
}
bool ScriptEngine::runScript(const std::string& path, Value* ret/* = nullptr */)
{
assert(!path.empty());
assert(_fileOperationDelegate.isValid());
std::string scriptBuffer = _fileOperationDelegate.onGetStringFromFile(path);
if (!scriptBuffer.empty())
{
return evalString(scriptBuffer.c_str(), scriptBuffer.length(), ret, path.c_str());
}
SE_LOGE("ScriptEngine::runScript script %s, buffer is empty!\n", path.c_str());
return false;
}
void ScriptEngine::clearException()
{
//IDEA:
}
void ScriptEngine::enableDebugger(const std::string& serverAddr, uint32_t port, bool isWait)
{
// empty implementation
_isDebuggerEnabled = true;
}
bool ScriptEngine::isDebuggerEnabled() const
{
return _isDebuggerEnabled;
}
void ScriptEngine::mainLoopUpdate()
{
// empty implementation
}
} // namespace se {
#endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_JSC

View File

@@ -0,0 +1,33 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
#include "ScriptEngine.hpp"
#include "Class.hpp"
#include "Object.hpp"
#include "../Value.hpp"
#include "../State.hpp"
#include "HelperMacros.h"
#include "Utils.hpp"

View File

@@ -0,0 +1,378 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#include "Utils.hpp"
#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_JSC
#include "Object.hpp"
#include "ScriptEngine.hpp"
#include "../HandleObject.hpp"
namespace se {
namespace internal {
namespace {
JSContextRef __cx = nullptr;
}
void setContext(JSContextRef cx)
{
__cx = cx;
}
bool defineProperty(Object* obj, const char* name, JSObjectCallAsFunctionCallback jsGetter, JSObjectCallAsFunctionCallback jsSetter)
{
bool ok = true;
Object* globalObject = ScriptEngine::getInstance()->getGlobalObject();
Value v;
ok = globalObject->getProperty("Object", &v);
if (!ok || !v.isObject())
{
SE_LOGD("ERROR: couldn't find Object\n");
return false;
}
Value definePropertyFunc;
ok = v.toObject()->getProperty("defineProperty", &definePropertyFunc);
if (!ok || !v.isObject())
{
SE_LOGD("ERROR: couldn't find Object.defineProperty\n");
return false;
}
ValueArray args;
args.reserve(3);
args.push_back(Value(obj));
args.push_back(Value(name));
HandleObject desc(Object::createPlainObject());
JSObjectRef getter = nullptr;
if (jsGetter != nullptr)
{
getter = JSObjectMakeFunctionWithCallback(__cx, nullptr, jsGetter);
}
JSObjectRef setter = nullptr;
if (jsSetter != nullptr)
{
setter = JSObjectMakeFunctionWithCallback(__cx, nullptr, jsSetter);
}
assert(getter != nullptr || setter != nullptr);
if (getter != nullptr)
{
HandleObject getterObj(Object::_createJSObject(nullptr, getter));
desc->setProperty("get", Value(getterObj));
}
if (setter != nullptr)
{
HandleObject setterObj(Object::_createJSObject(nullptr, setter));
desc->setProperty("set", Value(setterObj));
}
args.push_back(Value(desc));
definePropertyFunc.toObject()->call(args, nullptr);
return true;
}
void jsToSeArgs(JSContextRef cx, unsigned short argc, const JSValueRef* argv, ValueArray* outArr)
{
outArr->reserve(argc);
for (unsigned short i = 0; i < argc; ++i)
{
Value v;
jsToSeValue(cx, argv[i], &v);
outArr->push_back(v);
}
}
void seToJsArgs(JSContextRef cx, const ValueArray& args, JSValueRef* outArr)
{
for (size_t i = 0, len = args.size(); i < len; ++i)
{
seToJsValue(cx, args[i], &outArr[i]);
}
}
void jsToSeValue(JSContextRef cx, JSValueRef jsValue, Value* data)
{
if (JSValueIsNull(cx, jsValue))
{
data->setNull();
}
else if (JSValueIsUndefined(cx, jsValue))
{
data->setUndefined();
}
else if (JSValueIsNumber(cx, jsValue))
{
JSValueRef exception = nullptr;
double number = JSValueToNumber(cx, jsValue, &exception);
if (exception != nullptr)
{
ScriptEngine::getInstance()->_clearException(exception);
}
else
{
data->setNumber(number);
}
}
else if (JSValueIsBoolean(cx, jsValue))
{
data->setBoolean(JSValueToBoolean(cx, jsValue));
}
else if (JSValueIsString(cx, jsValue))
{
std::string str;
forceConvertJsValueToStdString(cx, jsValue, &str);
data->setString(str);
}
else if (JSValueIsObject(cx, jsValue))
{
JSValueRef exception = nullptr;
JSObjectRef jsobj = JSValueToObject(cx, jsValue, &exception);
if (exception != nullptr)
{
ScriptEngine::getInstance()->_clearException(exception);
}
else
{
void* nativePtr = internal::getPrivate(jsobj);
Object* obj = nullptr;
if (nativePtr != nullptr)
{
obj = Object::getObjectWithPtr(nativePtr);
}
if (obj == nullptr)
{
obj = Object::_createJSObject(nullptr, jsobj);
}
data->setObject(obj, true);
obj->decRef();
}
}
else
{
assert(false);
}
}
void seToJsValue(JSContextRef cx, const Value& v, JSValueRef* jsval)
{
switch (v.getType()) {
case Value::Type::Null:
*jsval = JSValueMakeNull(cx);
break;
case Value::Type::Number:
*jsval = JSValueMakeNumber(cx, v.toNumber());
break;
case Value::Type::String:
{
JSStringRef str = JSStringCreateWithUTF8CString(v.toString().c_str());
*jsval = JSValueMakeString(cx, str);
JSStringRelease(str);
}
break;
case Value::Type::Boolean:
*jsval = JSValueMakeBoolean(cx, v.toBoolean());
break;
case Value::Type::Object:
*jsval = v.toObject()->_getJSObject();
break;
default: // Undefined
*jsval = JSValueMakeUndefined(cx);
break;
}
}
void forceConvertJsValueToStdString(JSContextRef cx, JSValueRef jsval, std::string* ret, bool ignoreException/* = false */)
{
JSValueRef exception = nullptr;
JSStringRef jsstr = JSValueToStringCopy(cx, jsval, &exception);
if (exception != nullptr)
{
// NOTE: ignoreException is true in ScriptEngine::_formatException because it's already in the _clearException process.
// Adds this flag to avoid infinite loop of _clearException -> _formatException -> forceConvertJsValueToStdString -> _clearException -> ...
if (!ignoreException)
{
ScriptEngine::getInstance()->_clearException(exception);
}
ret->clear();
}
else
{
jsStringToStdString(cx, jsstr, ret);
}
if (jsstr != nullptr)
JSStringRelease(jsstr);
}
void jsStringToStdString(JSContextRef cx, JSStringRef jsStr, std::string* ret)
{
size_t len = JSStringGetLength(jsStr);
const size_t BUF_SIZE = len * 4 + 1;
char* buf = (char*)malloc(BUF_SIZE);
memset(buf, 0, BUF_SIZE);
JSStringGetUTF8CString(jsStr, buf, BUF_SIZE);
*ret = buf;
free(buf);
}
const char* KEY_PRIVATE_DATA = "__cc_private_data";
bool hasPrivate(JSObjectRef obj)
{
void* data = JSObjectGetPrivate(obj);
if (data != nullptr)
return true;
JSStringRef key = JSStringCreateWithUTF8CString(KEY_PRIVATE_DATA);
bool found = JSObjectHasProperty(__cx, obj, key);
JSStringRelease(key);
return found;
}
void setPrivate(JSObjectRef obj, void* data, JSObjectFinalizeCallback finalizeCb)
{
bool ok = JSObjectSetPrivate(obj, data);
if (ok)
{
return;
}
assert(finalizeCb);
Object* privateObj = Object::createObjectWithClass(__jsb_CCPrivateData_class);
privateObj->root();
internal::PrivateData* privateData = (internal::PrivateData*)malloc(sizeof(internal::PrivateData));
privateData->data = data;
privateData->finalizeCb = finalizeCb;
ok = JSObjectSetPrivate(privateObj->_getJSObject(), privateData);
assert(ok);
JSStringRef key = JSStringCreateWithUTF8CString(KEY_PRIVATE_DATA);
JSValueRef exception = nullptr;
JSObjectSetProperty(__cx, obj, key, privateObj->_getJSObject(), kJSPropertyAttributeDontEnum, &exception);
if (exception != nullptr)
{
ScriptEngine::getInstance()->_clearException(exception);
}
JSStringRelease(key);
privateObj->unroot();
privateObj->decRef();
}
void* getPrivate(JSObjectRef obj)
{
void* data = JSObjectGetPrivate(obj);
if (data != nullptr)
return data;
JSStringRef key = JSStringCreateWithUTF8CString(KEY_PRIVATE_DATA);
bool found = JSObjectHasProperty(__cx, obj, key);
if (found)
{
JSValueRef exception = nullptr;
JSValueRef privateDataVal = JSObjectGetProperty(__cx, obj, key, &exception);
do
{
if (exception != nullptr)
break;
JSObjectRef jsobj = JSValueToObject(__cx, privateDataVal, &exception);
if (exception != nullptr)
break;
internal::PrivateData* privateData = (internal::PrivateData*)JSObjectGetPrivate(jsobj);
assert(privateData != nullptr);
data = privateData->data;
} while(false);
if (exception != nullptr)
{
ScriptEngine::getInstance()->_clearException(exception);
}
}
JSStringRelease(key);
return data;
}
void clearPrivate(JSObjectRef obj)
{
void* data = JSObjectGetPrivate(obj);
if (data != nullptr)
{
JSObjectSetPrivate(obj, nullptr);
}
else
{
JSStringRef key = JSStringCreateWithUTF8CString(KEY_PRIVATE_DATA); //IDEA: cache the key string
if (JSObjectHasProperty(__cx, obj, key))
{
JSValueRef exception = nullptr;
do
{
JSValueRef value = JSObjectGetProperty(__cx, obj, key, &exception);
if (exception != nullptr)
break;
JSObjectRef propertyObj = JSValueToObject(__cx, value, &exception);
if (exception != nullptr)
break;
internal::PrivateData* privateData = (internal::PrivateData*)JSObjectGetPrivate(propertyObj);
free(privateData);
JSObjectSetPrivate(propertyObj, nullptr);
bool ok = JSObjectDeleteProperty(__cx, obj, key, nullptr);
assert(ok);
} while (false);
if (exception != nullptr)
{
ScriptEngine::getInstance()->_clearException(exception);
}
}
JSStringRelease(key);
}
}
}} // namespace se { namespace internal {
#endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_JSC

View File

@@ -0,0 +1,65 @@
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#pragma once
#include "../config.hpp"
#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_JSC
#include "Base.h"
#include "../Value.hpp"
namespace se {
namespace internal {
struct PrivateData
{
void* data;
JSObjectFinalizeCallback finalizeCb;
};
void setContext(JSContextRef cx);
bool defineProperty(Object* obj, const char* name, JSObjectCallAsFunctionCallback jsGetter, JSObjectCallAsFunctionCallback jsSetter);
void jsToSeArgs(JSContextRef cx, unsigned short argc, const JSValueRef* argv, ValueArray* outArr);
void seToJsArgs(JSContextRef cx, const ValueArray& args, JSValueRef* outArr);
void jsToSeValue(JSContextRef cx, JSValueRef jsval, Value* v);
void seToJsValue(JSContextRef cx, const Value& v, JSValueRef* jsval);
void forceConvertJsValueToStdString(JSContextRef cx, JSValueRef jsval, std::string* ret, bool ignoreException = false);
void jsStringToStdString(JSContextRef cx, JSStringRef jsStr, std::string* ret);
bool hasPrivate(JSObjectRef obj);
void setPrivate(JSObjectRef obj, void* data, JSObjectFinalizeCallback finalizeCb);
void* getPrivate(JSObjectRef obj);
void clearPrivate(JSObjectRef obj);
} // namespace internal {
} // namespace se {
#endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_JSC