diff options
author | Alon Zakai <alonzakai@gmail.com> | 2014-05-21 18:41:37 -0700 |
---|---|---|
committer | Alon Zakai <alonzakai@gmail.com> | 2014-05-21 18:41:37 -0700 |
commit | 876ae8a8b3867f146aaf545466c6170ad5407897 (patch) | |
tree | e6b21e6f2a50f7733d2c5ca55b7fcef95e12c4d4 | |
parent | 8ae137e8663b82f1fb41dd65deb76c8efd627641 (diff) | |
parent | 29839c4b7c6803c4741641e5ba8158516da2f680 (diff) |
Merge pull request #2371 from waywardmonkeys/upstream-from-imvu-again
Upstream from imvu again
-rw-r--r-- | src/embind/embind.js | 301 | ||||
-rw-r--r-- | src/embind/emval.js | 16 | ||||
-rw-r--r-- | system/include/emscripten/bind.h | 158 | ||||
-rw-r--r-- | system/include/emscripten/val.h | 14 | ||||
-rw-r--r-- | tests/embind/embind.test.js | 414 | ||||
-rw-r--r-- | tests/embind/embind_test.cpp | 277 |
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); |