aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlon Zakai <alonzakai@gmail.com>2014-05-21 18:41:37 -0700
committerAlon Zakai <alonzakai@gmail.com>2014-05-21 18:41:37 -0700
commit876ae8a8b3867f146aaf545466c6170ad5407897 (patch)
treee6b21e6f2a50f7733d2c5ca55b7fcef95e12c4d4
parent8ae137e8663b82f1fb41dd65deb76c8efd627641 (diff)
parent29839c4b7c6803c4741641e5ba8158516da2f680 (diff)
Merge pull request #2371 from waywardmonkeys/upstream-from-imvu-again
Upstream from imvu again
-rw-r--r--src/embind/embind.js301
-rw-r--r--src/embind/emval.js16
-rw-r--r--system/include/emscripten/bind.h158
-rw-r--r--system/include/emscripten/val.h14
-rw-r--r--tests/embind/embind.test.js414
-rw-r--r--tests/embind/embind_test.cpp277
6 files changed, 1051 insertions, 129 deletions
diff --git a/src/embind/embind.js b/src/embind/embind.js
index 4821c77b..124ea569 100644
--- a/src/embind/embind.js
+++ b/src/embind/embind.js
@@ -4,10 +4,12 @@
/*global readLatin1String*/
/*global __emval_register, _emval_handle_array, __emval_decref*/
/*global ___getTypeName*/
+/*global requireHandle*/
/*jslint sub:true*/ /* The symbols 'fromWireType' and 'toWireType' must be accessed via array notation to be closure-safe since craftInvokerFunction crafts functions as strings that can't be closured. */
var InternalError = Module['InternalError'] = extendError(Error, 'InternalError');
var BindingError = Module['BindingError'] = extendError(Error, 'BindingError');
var UnboundTypeError = Module['UnboundTypeError'] = extendError(BindingError, 'UnboundTypeError');
+var PureVirtualError = Module['PureVirtualError'] = extendError(BindingError, 'PureVirtualError');
function throwInternalError(message) {
throw new InternalError(message);
@@ -151,6 +153,59 @@ function _embind_repr(v) {
}
}
+// raw pointer -> instance
+var registeredInstances = {};
+
+function getBasestPointer(class_, ptr) {
+ if (ptr === undefined) {
+ throwBindingError('ptr should not be undefined');
+ }
+ while (class_.baseClass) {
+ ptr = class_.upcast(ptr);
+ class_ = class_.baseClass;
+ }
+ return ptr;
+}
+
+function registerInheritedInstance(class_, ptr, instance) {
+ ptr = getBasestPointer(class_, ptr);
+ if (registeredInstances.hasOwnProperty(ptr)) {
+ throwBindingError('Tried to register registered instance: ' + ptr);
+ } else {
+ registeredInstances[ptr] = instance;
+ }
+}
+
+function unregisterInheritedInstance(class_, ptr) {
+ ptr = getBasestPointer(class_, ptr);
+ if (registeredInstances.hasOwnProperty(ptr)) {
+ delete registeredInstances[ptr];
+ } else {
+ throwBindingError('Tried to unregister unregistered instance: ' + ptr);
+ }
+}
+
+function getInheritedInstance(class_, ptr) {
+ ptr = getBasestPointer(class_, ptr);
+ return registeredInstances[ptr];
+}
+
+function getInheritedInstanceCount() {
+ return Object.keys(registeredInstances).length;
+}
+Module['getInheritedInstanceCount'] = getInheritedInstanceCount;
+
+function getLiveInheritedInstances() {
+ var rv = [];
+ for (var k in registeredInstances) {
+ if (registeredInstances.hasOwnProperty(k)) {
+ rv.push(registeredInstances[k]);
+ }
+ }
+ return rv;
+}
+Module['getLiveInheritedInstances'] = getLiveInheritedInstances;
+
// typeID -> { toWireType: ..., fromWireType: ... }
var registeredTypes = {};
@@ -535,6 +590,9 @@ function __embind_register_emval(rawType, name) {
'argPackAdvance': 8,
'readValueFromPointer': simpleReadValueFromPointer,
destructorFunction: null, // This type does not need a destructor
+
+ // TODO: do we need a deleteObject here? write a test where
+ // emval is passed into JS via an interface
});
}
@@ -630,7 +688,7 @@ function craftInvokerFunction(humanName, argTypes, classType, cppInvokerFunc, cp
var argsList = "";
var argsListWired = "";
- for(var i = 0; i < argCount-2; ++i) {
+ for(var i = 0; i < argCount - 2; ++i) {
argsList += (i!==0?", ":"")+"arg"+i;
argsListWired += (i!==0?", ":"")+"arg"+i+"Wired";
}
@@ -665,7 +723,7 @@ function craftInvokerFunction(humanName, argTypes, classType, cppInvokerFunc, cp
invokerFnBody += "var thisWired = classParam.toWireType("+dtorStack+", this);\n";
}
- for(var i = 0; i < argCount-2; ++i) {
+ for(var i = 0; i < argCount - 2; ++i) {
invokerFnBody += "var arg"+i+"Wired = argType"+i+".toWireType("+dtorStack+", arg"+i+"); // "+argTypes[i+2].name+"\n";
args1.push("argType"+i);
args2.push(argTypes[i+2]);
@@ -684,7 +742,7 @@ function craftInvokerFunction(humanName, argTypes, classType, cppInvokerFunc, cp
invokerFnBody += "runDestructors(destructors);\n";
} else {
for(var i = isClassMethodFunc?1:2; i < argTypes.length; ++i) { // Skip return value at index 0 - it's not deleted here. Also skip class type if not a method.
- var paramName = (i === 1 ? "thisWired" : ("arg"+(i-2)+"Wired"));
+ var paramName = (i === 1 ? "thisWired" : ("arg"+(i - 2)+"Wired"));
if (argTypes[i].destructorFunction !== null) {
invokerFnBody += paramName+"_dtor("+paramName+"); // "+argTypes[i].name+"\n";
args1.push(paramName+"_dtor");
@@ -1124,14 +1182,14 @@ function RegisteredPointer(
}
}
-RegisteredPointer.prototype.getPointee = function(ptr) {
+RegisteredPointer.prototype.getPointee = function getPointee(ptr) {
if (this.rawGetPointee) {
ptr = this.rawGetPointee(ptr);
}
return ptr;
};
-RegisteredPointer.prototype.destructor = function(ptr) {
+RegisteredPointer.prototype.destructor = function destructor(ptr) {
if (this.rawDestructor) {
this.rawDestructor(ptr);
}
@@ -1140,7 +1198,13 @@ RegisteredPointer.prototype.destructor = function(ptr) {
RegisteredPointer.prototype['argPackAdvance'] = 8;
RegisteredPointer.prototype['readValueFromPointer'] = simpleReadValueFromPointer;
-RegisteredPointer.prototype['fromWireType'] = function(ptr) {
+RegisteredPointer.prototype['deleteObject'] = function deleteObject(handle) {
+ if (handle !== null) {
+ handle['delete']();
+ }
+};
+
+RegisteredPointer.prototype['fromWireType'] = function fromWireType(ptr) {
// ptr is a raw pointer (or a raw smartpointer)
// rawPointer is a maybe-null raw pointer
@@ -1150,6 +1214,13 @@ RegisteredPointer.prototype['fromWireType'] = function(ptr) {
return null;
}
+ var registeredInstance = getInheritedInstance(this.registeredClass, rawPointer);
+ if (undefined !== registeredInstance) {
+ var rv = registeredInstance['clone']();
+ this.destructor(ptr);
+ return rv;
+ }
+
function makeDefaultHandle() {
if (this.isSmartPointer) {
return makeClassHandle(this.registeredClass.instancePrototype, {
@@ -1225,7 +1296,7 @@ function getInstanceTypeName(handle) {
return handle.$$.ptrType.registeredClass.name;
}
-ClassHandle.prototype['isAliasOf'] = function(other) {
+ClassHandle.prototype['isAliasOf'] = function isAliasOf(other) {
if (!(this instanceof ClassHandle)) {
return false;
}
@@ -1255,19 +1326,24 @@ function throwInstanceAlreadyDeleted(obj) {
throwBindingError(getInstanceTypeName(obj) + ' instance already deleted');
}
-ClassHandle.prototype['clone'] = function() {
+ClassHandle.prototype['clone'] = function clone() {
if (!this.$$.ptr) {
throwInstanceAlreadyDeleted(this);
}
- var clone = Object.create(Object.getPrototypeOf(this), {
- $$: {
- value: shallowCopy(this.$$),
- }
- });
+ if (this.$$.preservePointerOnDelete) {
+ this.$$.count.value += 1;
+ return this;
+ } else {
+ var clone = Object.create(Object.getPrototypeOf(this), {
+ $$: {
+ value: shallowCopy(this.$$),
+ }
+ });
- clone.$$.count.value += 1;
- return clone;
+ clone.$$.count.value += 1;
+ return clone;
+ }
};
function runDestructor(handle) {
@@ -1283,16 +1359,20 @@ ClassHandle.prototype['delete'] = function ClassHandle_delete() {
if (!this.$$.ptr) {
throwInstanceAlreadyDeleted(this);
}
- if (this.$$.deleteScheduled) {
+
+ if (this.$$.deleteScheduled && !this.$$.preservePointerOnDelete) {
throwBindingError('Object already scheduled for deletion');
}
this.$$.count.value -= 1;
- if (0 === this.$$.count.value) {
+ var toDelete = 0 === this.$$.count.value;
+ if (toDelete) {
runDestructor(this);
}
- this.$$.smartPtr = undefined;
- this.$$.ptr = undefined;
+ if (!this.$$.preservePointerOnDelete) {
+ this.$$.smartPtr = undefined;
+ this.$$.ptr = undefined;
+ }
};
var deletionQueue = [];
@@ -1305,7 +1385,7 @@ ClassHandle.prototype['deleteLater'] = function deleteLater() {
if (!this.$$.ptr) {
throwInstanceAlreadyDeleted(this);
}
- if (this.$$.deleteScheduled) {
+ if (this.$$.deleteScheduled && !this.$$.preservePointerOnDelete) {
throwBindingError('Object already scheduled for deletion');
}
deletionQueue.push(this);
@@ -1351,12 +1431,15 @@ function RegisteredClass(
this.getActualType = getActualType;
this.upcast = upcast;
this.downcast = downcast;
+ this.pureVirtualFunctions = [];
}
function shallowCopy(o) {
var rv = {};
for (var k in o) {
- rv[k] = o[k];
+ if (Object.prototype.hasOwnProperty.call(o, k)) {
+ rv[k] = o[k];
+ }
}
return rv;
}
@@ -1491,12 +1574,12 @@ function __embind_register_class_constructor(
if (undefined !== classType.registeredClass.constructor_body[argCount - 1]) {
throw new BindingError("Cannot register multiple constructors with identical number of parameters (" + (argCount-1) + ") for class '" + classType.name + "'! Overload resolution is currently only performed using the parameter count, not actual type info!");
}
- classType.registeredClass.constructor_body[argCount - 1] = function() {
+ classType.registeredClass.constructor_body[argCount - 1] = function unboundTypeHandler() {
throwUnboundTypeError('Cannot construct ' + classType.name + ' due to unbound types', rawArgTypes);
};
whenDependentTypesAreResolved([], rawArgTypes, function(argTypes) {
- classType.registeredClass.constructor_body[argCount - 1] = function() {
+ classType.registeredClass.constructor_body[argCount - 1] = function constructor_body() {
if (arguments.length !== argCount - 1) {
throwBindingError(humanName + ' called with ' + arguments.length + ' arguments, expected ' + (argCount-1));
}
@@ -1569,7 +1652,8 @@ function __embind_register_class_function(
rawArgTypesAddr, // [ReturnType, ThisType, Args...]
invokerSignature,
rawInvoker,
- context
+ context,
+ isPureVirtual
) {
var rawArgTypes = heap32VectorToArray(argCount, rawArgTypesAddr);
methodName = readLatin1String(methodName);
@@ -1579,21 +1663,25 @@ function __embind_register_class_function(
classType = classType[0];
var humanName = classType.name + '.' + methodName;
- var unboundTypesHandler = function() {
+ if (isPureVirtual) {
+ classType.registeredClass.pureVirtualFunctions.push(methodName);
+ }
+
+ function unboundTypesHandler() {
throwUnboundTypeError('Cannot call ' + humanName + ' due to unbound types', rawArgTypes);
- };
+ }
var proto = classType.registeredClass.instancePrototype;
var method = proto[methodName];
- if (undefined === method || (undefined === method.overloadTable && method.className !== classType.name && method.argCount === argCount-2)) {
+ if (undefined === method || (undefined === method.overloadTable && method.className !== classType.name && method.argCount === argCount - 2)) {
// This is the first overload to be registered, OR we are replacing a function in the base class with a function in the derived class.
- unboundTypesHandler.argCount = argCount-2;
+ unboundTypesHandler.argCount = argCount - 2;
unboundTypesHandler.className = classType.name;
proto[methodName] = unboundTypesHandler;
} else {
// There was an existing function with the same name registered. Set up a function overload routing table.
ensureOverloadTable(proto, methodName, humanName);
- proto[methodName].overloadTable[argCount-2] = unboundTypesHandler;
+ proto[methodName].overloadTable[argCount - 2] = unboundTypesHandler;
}
whenDependentTypesAreResolved([], rawArgTypes, function(argTypes) {
@@ -1605,7 +1693,7 @@ function __embind_register_class_function(
if (undefined === proto[methodName].overloadTable) {
proto[methodName] = memberFunction;
} else {
- proto[methodName].overloadTable[argCount-2] = memberFunction;
+ proto[methodName].overloadTable[argCount - 2] = memberFunction;
}
return [];
@@ -1614,53 +1702,6 @@ function __embind_register_class_function(
});
}
-function __embind_register_class_class_function(
- rawClassType,
- methodName,
- argCount,
- rawArgTypesAddr,
- invokerSignature,
- rawInvoker,
- fn
-) {
- var rawArgTypes = heap32VectorToArray(argCount, rawArgTypesAddr);
- methodName = readLatin1String(methodName);
- rawInvoker = requireFunction(invokerSignature, rawInvoker);
- whenDependentTypesAreResolved([], [rawClassType], function(classType) {
- classType = classType[0];
- var humanName = classType.name + '.' + methodName;
-
- var unboundTypesHandler = function() {
- throwUnboundTypeError('Cannot call ' + humanName + ' due to unbound types', rawArgTypes);
- };
-
- var proto = classType.registeredClass.constructor;
- if (undefined === proto[methodName]) {
- // This is the first function to be registered with this name.
- unboundTypesHandler.argCount = argCount-1;
- proto[methodName] = unboundTypesHandler;
- } else {
- // There was an existing function with the same name registered. Set up a function overload routing table.
- ensureOverloadTable(proto, methodName, humanName);
- proto[methodName].overloadTable[argCount-1] = unboundTypesHandler;
- }
-
- whenDependentTypesAreResolved([], rawArgTypes, function(argTypes) {
- // Replace the initial unbound-types-handler stub with the proper function. If multiple overloads are registered,
- // the function handlers go into an overload table.
- var invokerArgsArray = [argTypes[0] /* return value */, null /* no class 'this'*/].concat(argTypes.slice(1) /* actual params */);
- var func = craftInvokerFunction(humanName, invokerArgsArray, null /* no class 'this'*/, rawInvoker, fn);
- if (undefined === proto[methodName].overloadTable) {
- proto[methodName] = func;
- } else {
- proto[methodName].overloadTable[argCount-1] = func;
- }
- return [];
- });
- return [];
- });
-}
-
function __embind_register_class_property(
classType,
fieldName,
@@ -1730,6 +1771,112 @@ function __embind_register_class_property(
});
}
+function __embind_register_class_class_function(
+ rawClassType,
+ methodName,
+ argCount,
+ rawArgTypesAddr,
+ invokerSignature,
+ rawInvoker,
+ fn
+) {
+ var rawArgTypes = heap32VectorToArray(argCount, rawArgTypesAddr);
+ methodName = readLatin1String(methodName);
+ rawInvoker = requireFunction(invokerSignature, rawInvoker);
+ whenDependentTypesAreResolved([], [rawClassType], function(classType) {
+ classType = classType[0];
+ var humanName = classType.name + '.' + methodName;
+
+ function unboundTypesHandler() {
+ throwUnboundTypeError('Cannot call ' + humanName + ' due to unbound types', rawArgTypes);
+ }
+
+ var proto = classType.registeredClass.constructor;
+ if (undefined === proto[methodName]) {
+ // This is the first function to be registered with this name.
+ unboundTypesHandler.argCount = argCount-1;
+ proto[methodName] = unboundTypesHandler;
+ } else {
+ // There was an existing function with the same name registered. Set up a function overload routing table.
+ ensureOverloadTable(proto, methodName, humanName);
+ proto[methodName].overloadTable[argCount-1] = unboundTypesHandler;
+ }
+
+ whenDependentTypesAreResolved([], rawArgTypes, function(argTypes) {
+ // Replace the initial unbound-types-handler stub with the proper function. If multiple overloads are registered,
+ // the function handlers go into an overload table.
+ var invokerArgsArray = [argTypes[0] /* return value */, null /* no class 'this'*/].concat(argTypes.slice(1) /* actual params */);
+ var func = craftInvokerFunction(humanName, invokerArgsArray, null /* no class 'this'*/, rawInvoker, fn);
+ if (undefined === proto[methodName].overloadTable) {
+ proto[methodName] = func;
+ } else {
+ proto[methodName].overloadTable[argCount-1] = func;
+ }
+ return [];
+ });
+ return [];
+ });
+}
+
+function __embind_create_inheriting_constructor(constructorName, wrapperType, properties) {
+ constructorName = readLatin1String(constructorName);
+ wrapperType = requireRegisteredType(wrapperType, 'wrapper');
+ properties = requireHandle(properties);
+
+ var arraySlice = [].slice;
+
+ var registeredClass = wrapperType.registeredClass;
+ var wrapperPrototype = registeredClass.instancePrototype;
+ var baseClass = registeredClass.baseClass;
+ var baseClassPrototype = baseClass.instancePrototype;
+ var baseConstructor = registeredClass.baseClass.constructor;
+ var ctor = createNamedFunction(constructorName, function() {
+ registeredClass.baseClass.pureVirtualFunctions.forEach(function(name) {
+ if (this[name] === baseClassPrototype[name]) {
+ throw new PureVirtualError('Pure virtual function ' + name + ' must be implemented in JavaScript');
+ }
+ }.bind(this));
+
+ Object.defineProperty(this, '__parent', {
+ value: wrapperPrototype
+ });
+ this.__construct.apply(this, arraySlice.call(arguments));
+ });
+
+ // It's a little nasty that we're modifying the wrapper prototype here.
+
+ wrapperPrototype.__construct = function __construct() {
+ if (this === wrapperPrototype) {
+ throwBindingError("Pass correct 'this' to __construct");
+ }
+
+ var inner = baseConstructor.implement.apply(
+ undefined,
+ [this].concat(arraySlice.call(arguments)));
+ var $$ = inner.$$;
+ inner.notifyOnDestruction();
+ $$.preservePointerOnDelete = true;
+ Object.defineProperty(this, '$$', {
+ value: $$
+ });
+ registerInheritedInstance(registeredClass, $$.ptr, this);
+ };
+
+ wrapperPrototype.__destruct = function __destruct() {
+ if (this === wrapperPrototype) {
+ throwBindingError("Pass correct 'this' to __destruct");
+ }
+
+ unregisterInheritedInstance(registeredClass, this.$$.ptr);
+ };
+
+ ctor.prototype = Object.create(wrapperPrototype);
+ for (var p in properties) {
+ ctor.prototype[p] = properties[p];
+ }
+ return __emval_register(ctor);
+}
+
var char_0 = '0'.charCodeAt(0);
var char_9 = '9'.charCodeAt(0);
function makeLegalFunctionName(name) {
diff --git a/src/embind/emval.js b/src/embind/emval.js
index ebec3881..1661bc02 100644
--- a/src/embind/emval.js
+++ b/src/embind/emval.js
@@ -265,7 +265,14 @@ function __emval_get_method_caller(argCount, argTypes) {
" args += argType" + i + ".argPackAdvance;\n";
}
functionBody +=
- " var rv = handle[name](" + argsList + ");\n" +
+ " var rv = handle[name](" + argsList + ");\n";
+ for (var i = 0; i < argCount - 1; ++i) {
+ if (types[i + 1]['deleteObject']) {
+ functionBody +=
+ " argType" + i + ".deleteObject(arg" + i + ");\n";
+ }
+ }
+ functionBody +=
" return retType.toWireType(destructors, rv);\n" +
"};\n";
@@ -281,10 +288,13 @@ function __emval_call_method(caller, handle, methodName, destructorsRef, args) {
return caller(handle, methodName, allocateDestructors(destructorsRef), args);
}
-function __emval_has_function(handle, name) {
+function __emval_has_function(handle, name, classType) {
handle = requireHandle(handle);
name = getStringOrSymbol(name);
- return handle[name] instanceof Function;
+ classType = requireRegisteredType(classType, 'class wrapper filter');
+
+ var filter = classType.registeredClass.instancePrototype[name];
+ return (handle[name] instanceof Function) && (filter === undefined || handle[name] !== filter);
}
function __emval_typeof(handle) {
diff --git a/system/include/emscripten/bind.h b/system/include/emscripten/bind.h
index eede0755..7bc28ff6 100644
--- a/system/include/emscripten/bind.h
+++ b/system/include/emscripten/bind.h
@@ -149,7 +149,8 @@ namespace emscripten {
TYPEID argTypes[],
const char* invokerSignature,
GenericFunction invoker,
- void* context);
+ void* context,
+ unsigned isPureVirtual);
void _embind_register_class_property(
TYPEID classType,
@@ -172,6 +173,11 @@ namespace emscripten {
GenericFunction invoker,
GenericFunction method);
+ EM_VAL _embind_create_inheriting_constructor(
+ const char* constructorName,
+ TYPEID wrapperType,
+ EM_VAL properties);
+
void _embind_register_enum(
TYPEID enumType,
const char* name,
@@ -276,6 +282,33 @@ namespace emscripten {
return method;
}
+ namespace internal {
+ // this should be in <type_traits>, but alas, it's not
+ template<typename T> struct remove_class;
+ template<typename C, typename R, typename... A>
+ struct remove_class<R(C::*)(A...)> { using type = R(A...); };
+ template<typename C, typename R, typename... A>
+ struct remove_class<R(C::*)(A...) const> { using type = R(A...); };
+ template<typename C, typename R, typename... A>
+ struct remove_class<R(C::*)(A...) volatile> { using type = R(A...); };
+ template<typename C, typename R, typename... A>
+ struct remove_class<R(C::*)(A...) const volatile> { using type = R(A...); };
+
+ template<typename LambdaType>
+ struct CalculateLambdaSignature {
+ using type = typename std::add_pointer<
+ typename remove_class<
+ decltype(&LambdaType::operator())
+ >::type
+ >::type;
+ };
+ }
+
+ template<typename LambdaType>
+ typename internal::CalculateLambdaSignature<LambdaType>::type optional_override(const LambdaType& fp) {
+ return fp;
+ }
+
////////////////////////////////////////////////////////////////////////////////
// Invoker
////////////////////////////////////////////////////////////////////////////////
@@ -873,40 +906,55 @@ namespace emscripten {
};
};
+
////////////////////////////////////////////////////////////////////////////////
// CLASSES
////////////////////////////////////////////////////////////////////////////////
+ namespace internal {
+ class WrapperBase {
+ public:
+ void setNotifyJSOnDestruction(bool notify) {
+ notifyJSOnDestruction = notify;
+ }
+
+ protected:
+ bool notifyJSOnDestruction = false;
+ };
+ }
+
// abstract classes
template<typename T>
- class wrapper : public T {
+ class wrapper : public T, public internal::WrapperBase {
public:
typedef T class_type;
- explicit wrapper(val&& wrapped)
- : wrapped(std::forward<val>(wrapped))
+ template<typename... Args>
+ explicit wrapper(val&& wrapped, Args&&... args)
+ : T(std::forward<Args>(args)...)
+ , wrapped(std::forward<val>(wrapped))
{}
+ ~wrapper() {
+ if (notifyJSOnDestruction) {
+ call<void>("__destruct");
+ }
+ }
+
template<typename ReturnType, typename... Args>
ReturnType call(const char* name, Args&&... args) const {
return wrapped.call<ReturnType>(name, std::forward<Args>(args)...);
}
- template<typename ReturnType, typename... Args, typename Default>
- ReturnType optional_call(const char* name, Default def, Args&&... args) const {
- if (wrapped.has_function(name)) {
- return call<ReturnType>(name, std::forward<Args>(args)...);
- } else {
- return def();
- }
- }
-
private:
val wrapped;
};
-#define EMSCRIPTEN_WRAPPER(T) \
- T(::emscripten::val&& v): wrapper(std::forward<::emscripten::val>(v)) {}
+#define EMSCRIPTEN_WRAPPER(T) \
+ template<typename... Args> \
+ T(::emscripten::val&& v, Args&&... args) \
+ : wrapper(std::forward<::emscripten::val>(v), std::forward<Args>(args)...) \
+ {}
namespace internal {
struct NoBaseClass {
@@ -987,6 +1035,45 @@ namespace emscripten {
SmartPtrIfNeeded(U&, const char*) {
}
};
+
+ template<typename WrapperType>
+ val wrapped_extend(const std::string& name, const val& properties) {
+ return val::take_ownership(_embind_create_inheriting_constructor(
+ name.c_str(),
+ TypeID<WrapperType>::get(),
+ properties.__get_handle()));
+ }
+ };
+
+ struct pure_virtual {
+ template<typename InputType, int Index>
+ struct Transform {
+ typedef InputType type;
+ };
+ };
+
+ namespace internal {
+ template<typename... Policies>
+ struct isPureVirtual;
+
+ template<typename... Rest>
+ struct isPureVirtual<pure_virtual, Rest...> {
+ static constexpr bool value = true;
+ };
+
+ template<typename T, typename... Rest>
+ struct isPureVirtual<T, Rest...> {
+ static constexpr bool value = isPureVirtual<Rest...>::value;
+ };
+
+ template<>
+ struct isPureVirtual<> {
+ static constexpr bool value = false;
+ };
+ }
+
+ template<typename... ConstructorArgs>
+ struct constructor {
};
template<typename ClassType, typename BaseSpecifier = internal::NoBaseClass>
@@ -1095,18 +1182,38 @@ namespace emscripten {
return *this;
}
- template<typename WrapperType, typename PointerType = WrapperType*>
- const class_& allow_subclass(const char* wrapperClassName, const char* pointerName = "<UnknownPointerName>") const {
+ template<typename WrapperType, typename PointerType = WrapperType*, typename... ConstructorArgs>
+ const class_& allow_subclass(
+ const char* wrapperClassName,
+ const char* pointerName = "<UnknownPointerName>",
+ ::emscripten::constructor<ConstructorArgs...> = ::emscripten::constructor<ConstructorArgs...>()
+ ) const {
using namespace internal;
auto cls = class_<WrapperType, base<ClassType>>(wrapperClassName)
+ .function("notifyOnDestruction", select_overload<void(WrapperType&)>([](WrapperType& wrapper) {
+ wrapper.setNotifyJSOnDestruction(true);
+ }))
;
SmartPtrIfNeeded<PointerType> _(cls, pointerName);
- return class_function(
- "implement",
- &wrapped_new<PointerType, WrapperType, val>,
- allow_raw_pointer<ret_val>());
+ return
+ class_function(
+ "implement",
+ &wrapped_new<PointerType, WrapperType, val, ConstructorArgs...>,
+ allow_raw_pointer<ret_val>())
+ .class_function(
+ "extend",
+ &wrapped_extend<WrapperType>)
+ ;
+ }
+
+ template<typename WrapperType, typename... ConstructorArgs>
+ const class_& allow_subclass(
+ const char* wrapperClassName,
+ ::emscripten::constructor<ConstructorArgs...> constructor
+ ) const {
+ return allow_subclass<WrapperType, WrapperType*>(wrapperClassName, "<UnknownPointerName>", constructor);
}
template<typename ReturnType, typename... Args, typename... Policies>
@@ -1123,7 +1230,8 @@ namespace emscripten {
args.types,
getSignature(invoker),
reinterpret_cast<GenericFunction>(invoker),
- getContext(memberFunction));
+ getContext(memberFunction),
+ isPureVirtual<Policies...>::value);
return *this;
}
@@ -1141,7 +1249,8 @@ namespace emscripten {
args.types,
getSignature(invoker),
reinterpret_cast<GenericFunction>(invoker),
- getContext(memberFunction));
+ getContext(memberFunction),
+ isPureVirtual<Policies...>::value);
return *this;
}
@@ -1158,7 +1267,8 @@ namespace emscripten {
args.types,
getSignature(invoke),
reinterpret_cast<GenericFunction>(invoke),
- getContext(function));
+ getContext(function),
+ false);
return *this;
}
diff --git a/system/include/emscripten/val.h b/system/include/emscripten/val.h
index bfd8610a..31f5923e 100644
--- a/system/include/emscripten/val.h
+++ b/system/include/emscripten/val.h
@@ -61,7 +61,8 @@ namespace emscripten {
EM_VAR_ARGS argv);
bool _emval_has_function(
EM_VAL value,
- const char* methodName);
+ const char* methodName,
+ internal::TYPEID filter);
EM_VAL _emval_typeof(EM_VAL value);
}
@@ -392,8 +393,10 @@ namespace emscripten {
return MethodCaller<ReturnValue, Args...>::call(handle, name, std::forward<Args>(args)...);
}
- bool has_function(const char* name) const {
- return _emval_has_function(handle, name);
+ template<typename ClassType>
+ bool has_implementation_defined_function(const char* name) const {
+ using namespace internal;
+ return _emval_has_function(handle, name, TypeID<ClassType>::get());
}
template<typename T>
@@ -411,6 +414,11 @@ namespace emscripten {
return fromGenericWireType<T>(result);
}
+ // private: TODO: use a friend?
+ internal::EM_VAL __get_handle() const {
+ return handle;
+ }
+
val typeof() const {
return val(_emval_typeof(handle));
}
diff --git a/tests/embind/embind.test.js b/tests/embind/embind.test.js
index 53d3988a..a6b2e98c 100644
--- a/tests/embind/embind.test.js
+++ b/tests/embind/embind.test.js
@@ -1548,7 +1548,7 @@ module({
});
});
- BaseFixture.extend("abstract methods", function() {
+ BaseFixture.extend("implementing abstract methods with JS objects", function() {
test("can call abstract methods", function() {
var obj = cm.getAbstractClass();
assert.equal("from concrete", obj.abstractMethod());
@@ -1580,7 +1580,8 @@ module({
};
var impl = cm.AbstractClass.implement(new MyImplementation);
- assert.equal(expected, impl.optionalMethod(expected));
+ // TODO: remove .implement() as a public API. It interacts poorly with Class.extend.
+ //assert.equal(expected, impl.optionalMethod(expected));
assert.equal(expected, cm.callOptionalMethod(impl, expected));
impl.delete();
});
@@ -1588,7 +1589,8 @@ module({
test("if not implemented then optional method runs default", function() {
var impl = cm.AbstractClass.implement({});
assert.equal("optionalfoo", impl.optionalMethod("foo"));
- assert.equal("optionalfoo", cm.callOptionalMethod(impl, "foo"));
+ // TODO: remove .implement() as a public API. It interacts poorly with Class.extend.
+ //assert.equal("optionalfoo", cm.callOptionalMethod(impl, "foo"));
impl.delete();
});
@@ -1638,6 +1640,366 @@ module({
impl.delete();
});
+
+ test("returning a cached new shared pointer from interfaces implemented in JS code does not leak", function() {
+ var derived = cm.embind_test_return_smart_derived_ptr();
+ var impl = cm.AbstractClass.implement({
+ returnsSharedPtr: function() {
+ return derived;
+ }
+ });
+ cm.callReturnsSharedPtrMethod(impl);
+ impl.delete();
+ derived.delete();
+ // Let the memory leak test superfixture check that no leaks occurred.
+ });
+ });
+
+ BaseFixture.extend("constructor prototype class inheritance", function() {
+ var Empty = cm.AbstractClass.extend("Empty", {
+ abstractMethod: function() {
+ }
+ });
+
+ test("can extend, construct, and delete", function() {
+ var instance = new Empty;
+ instance.delete();
+ });
+
+ test("properties set in constructor are externally visible", function() {
+ var HasProperty = cm.AbstractClass.extend("HasProperty", {
+ __construct: function(x) {
+ this.__parent.__construct.call(this);
+ this.property = x;
+ },
+ abstractMethod: function() {
+ }
+ });
+ var instance = new HasProperty(10);
+ assert.equal(10, instance.property);
+ instance.delete();
+ });
+
+ test("pass derived object to c++", function() {
+ var Implementation = cm.AbstractClass.extend("Implementation", {
+ abstractMethod: function() {
+ return "abc";
+ },
+ });
+ var instance = new Implementation;
+ var result = cm.callAbstractMethod(instance);
+ instance.delete();
+ assert.equal("abc", result);
+ });
+
+ test("properties set in constructor are visible in overridden methods", function() {
+ var HasProperty = cm.AbstractClass.extend("HasProperty", {
+ __construct: function(x) {
+ this.__parent.__construct.call(this);
+ this.x = x;
+ },
+ abstractMethod: function() {
+ return this.x;
+ },
+ });
+ var instance = new HasProperty("xyz");
+ var result = cm.callAbstractMethod(instance);
+ instance.delete();
+ assert.equal("xyz", result);