diff options
author | Alon Zakai <alonzakai@gmail.com> | 2012-10-01 10:48:28 -0700 |
---|---|---|
committer | Alon Zakai <alonzakai@gmail.com> | 2012-10-01 10:48:28 -0700 |
commit | 375982d9c18ec64764c9ab14406e5712493e2cb3 (patch) | |
tree | 809a0bc40c172aa6fdd5c6312a2e1ba72b5fbb7f | |
parent | 7eaa78060c34489c7e56193c725641303d520f31 (diff) | |
parent | 58056b5383b1cdcd4f537fad82f9d4a03fb2556e (diff) |
Merge pull request #592 from imvu/embind-pull-request
Embind pull request
-rw-r--r-- | src/embind/embind.js | 619 | ||||
-rw-r--r-- | src/embind/emval.js | 111 | ||||
-rw-r--r-- | system/include/emscripten/bind.h | 645 | ||||
-rw-r--r-- | system/include/emscripten/val.h | 177 | ||||
-rw-r--r-- | system/include/emscripten/wire.h | 223 | ||||
-rwxr-xr-x | system/lib/embind/bind.cpp | 34 | ||||
-rw-r--r-- | tests/embind/embind_test.cpp | 335 | ||||
-rw-r--r-- | tests/embind/embind_test.js | 341 |
8 files changed, 2485 insertions, 0 deletions
diff --git a/src/embind/embind.js b/src/embind/embind.js new file mode 100644 index 00000000..fff19d86 --- /dev/null +++ b/src/embind/embind.js @@ -0,0 +1,619 @@ +/*global Module*/ +/*global _malloc, _free, _memcpy*/ +/*global FUNCTION_TABLE, HEAP32*/ +/*global Pointer_stringify, writeStringToMemory*/ +/*global __emval_register, _emval_handle_array, __emval_decref*/ + +function createNamedFunction(name, body) { + /*jshint evil:true*/ + return new Function( + "body", + "return function " + name + "() {\n" + + " return body.apply(this, arguments);\n" + + "};\n" + )(body); +} + +function _embind_repr(v) { + var t = typeof v; + if (t === 'object' || t === 'array' || t === 'function') { + return v.toString(); + } else { + return '' + v; + } +} + +var typeRegistry = {}; + +function validateType(type, name) { + if (!type) { + throw new BindingError('type "' + name + '" must have a positive integer typeid pointer'); + } + if (undefined !== typeRegistry[type]) { + throw new BindingError('cannot register type "' + name + '" twice'); + } +} + +function __embind_register_void(voidType, name) { + name = Pointer_stringify(name); + validateType(voidType, name); + typeRegistry[voidType] = { + name: name, + fromWireType: function() { + return undefined; + } + }; +} + +function __embind_register_bool(boolType, name, trueValue, falseValue) { + name = Pointer_stringify(name); + validateType(boolType, name); + typeRegistry[boolType] = { + name: name, + toWireType: function(destructors, o) { + return o ? trueValue : falseValue; + }, + fromWireType: function(wt) { + return wt === trueValue; + }, + }; +} + +function __embind_register_integer(primitiveType, name) { + name = Pointer_stringify(name); + validateType(primitiveType, name); + typeRegistry[primitiveType] = { + name: name, + toWireType: function(destructors, value) { + if (typeof value !== "number") { + throw new TypeError('Cannot convert "' + _embind_repr(value) + '" to ' + name); + } + return value | 0; + }, + fromWireType: function(value) { + return value; + } + }; +} + +function __embind_register_float(primitiveType, name) { + name = Pointer_stringify(name); + validateType(primitiveType, name); + typeRegistry[primitiveType] = { + name: name, + toWireType: function(destructors, value) { + if (typeof value !== "number") { + throw new TypeError('Cannot convert "' + _embind_repr(value) + '" to ' + name); + } + return value; + }, + fromWireType: function(value) { + return value; + } + }; +} + +function __embind_register_cstring(stringType, name) { + name = Pointer_stringify(name); + validateType(stringType, name); + typeRegistry[stringType] = { + name: name, + toWireType: function(destructors, value) { + var ptr = _malloc(value.length + 1); + writeStringToMemory(value, ptr); + destructors.push(_free); + destructors.push(ptr); + return ptr; + }, + fromWireType: function(value) { + var rv = Pointer_stringify(value); + _free(value); + return rv; + } + }; +} + +function __embind_register_emval(emvalType, name) { + name = Pointer_stringify(name); + validateType(emvalType, name); + typeRegistry[emvalType] = { + name: name, + toWireType: function(destructors, value) { + return __emval_register(value); + }, + fromWireType: function(handle) { + var rv = _emval_handle_array[handle].value; + __emval_decref(handle); + return rv; + } + }; +} + +var BindingError = Error; +/** @expose */ +Module.BindingError = BindingError; + +function typeName(typeID) { + // could use our carnal knowledge of RTTI but for now just return the pointer... + return typeID; +} + +function requireRegisteredType(type, humanName) { + var impl = typeRegistry[type]; + if (undefined === impl) { + throw new BindingError(humanName + " has unknown type: " + typeName(type)); + } + return impl; +} + +function requireArgumentTypes(argCount, argTypes, name) { + var argTypeImpls = new Array(argCount); + for (var i = 0; i < argCount; ++i) { + var argType = HEAP32[(argTypes >> 2) + i]; + argTypeImpls[i] = requireRegisteredType(argType, name + " parameter " + i); + } + return argTypeImpls; +} + +function runDestructors(destructors) { + while (destructors.length) { + var ptr = destructors.pop(); + var del = destructors.pop(); + del(ptr); + } +} + +function __embind_register_function(name, returnType, argCount, argTypes, invoker, fn) { + name = Pointer_stringify(name); + returnType = requireRegisteredType(returnType, "Function " + name + " return value"); + invoker = FUNCTION_TABLE[invoker]; + argTypes = requireArgumentTypes(argCount, argTypes, name); + + Module[name] = function() { + if (arguments.length !== argCount) { + throw new BindingError('emscripten binding function ' + name + ' called with ' + arguments.length + ' arguments, expected ' + argCount); + } + var destructors = []; + var args = new Array(argCount + 1); + args[0] = fn; + for (var i = 0; i < argCount; ++i) { + args[i + 1] = argTypes[i].toWireType(destructors, arguments[i]); + } + var rv = returnType.fromWireType(invoker.apply(null, args)); + runDestructors(destructors); + return rv; + }; +} + +function __embind_register_tuple(tupleType, name, constructor, destructor) { + name = Pointer_stringify(name); + constructor = FUNCTION_TABLE[constructor]; + destructor = FUNCTION_TABLE[destructor]; + + var elements = []; + + typeRegistry[tupleType] = { + name: name, + elements: elements, + fromWireType: function(ptr) { + var len = elements.length; + var rv = new Array(len); + for (var i = 0; i < len; ++i) { + rv[i] = elements[i].read(ptr); + } + destructor(ptr); + return rv; + }, + toWireType: function(destructors, o) { + var len = elements.length; + if (len !== o.length) { + throw new TypeError("Incorrect number of tuple elements"); + } + var ptr = constructor(); + for (var i = 0; i < len; ++i) { + elements[i].write(ptr, o[i]); + } + destructors.push(destructor); + destructors.push(ptr); + return ptr; + } + }; +} + +function copyMemberPointer(memberPointer, memberPointerSize) { + var copy = _malloc(memberPointerSize); + if (!copy) { + throw new Error('Failed to allocate member pointer copy'); + } + _memcpy(copy, memberPointer, memberPointerSize); + return copy; +} + +function __embind_register_tuple_element( + tupleType, + elementType, + getter, + setter, + memberPointerSize, + memberPointer +) { + tupleType = requireRegisteredType(tupleType, 'tuple'); + elementType = requireRegisteredType(elementType, "element " + tupleType.name + "[" + tupleType.elements.length + "]"); + getter = FUNCTION_TABLE[getter]; + setter = FUNCTION_TABLE[setter]; + memberPointer = copyMemberPointer(memberPointer, memberPointerSize); + + tupleType.elements.push({ + read: function(ptr) { + return elementType.fromWireType(getter(ptr, memberPointer)); + }, + write: function(ptr, o) { + var destructors = []; + setter(ptr, memberPointer, elementType.toWireType(destructors, o)); + runDestructors(destructors); + } + }); +} + +function __embind_register_tuple_element_accessor( + tupleType, + elementType, + staticGetter, + getterSize, + getter, + staticSetter, + setterSize, + setter +) { + tupleType = requireRegisteredType(tupleType, 'tuple'); + elementType = requireRegisteredType(elementType, "element " + tupleType.name + "[" + tupleType.elements.length + "]"); + staticGetter = FUNCTION_TABLE[staticGetter]; + getter = copyMemberPointer(getter, getterSize); + staticSetter = FUNCTION_TABLE[staticSetter]; + setter = copyMemberPointer(setter, setterSize); + + tupleType.elements.push({ + read: function(ptr) { + return elementType.fromWireType(staticGetter(ptr, HEAP32[getter >> 2])); + }, + write: function(ptr, o) { + var destructors = []; + staticSetter( + ptr, + HEAP32[setter >> 2], + elementType.toWireType(destructors, o)); + runDestructors(destructors); + } + }); +} + +function __embind_register_struct( + structType, + name, + constructor, + destructor +) { + name = Pointer_stringify(name); + constructor = FUNCTION_TABLE[constructor]; + destructor = FUNCTION_TABLE[destructor]; + + typeRegistry[structType] = { + fields: {}, + fromWireType: function(ptr) { + var fields = this.fields; + var rv = {}; + for (var i in fields) { + rv[i] = fields[i].read(ptr); + } + destructor(ptr); + return rv; + }, + toWireType: function(destructors, o) { + var fields = this.fields; + for (var fieldName in fields) { + if (!(fieldName in o)) { + throw new TypeError('Missing field'); + } + } + var ptr = constructor(); + for (var fieldName in fields) { + fields[fieldName].write(ptr, o[fieldName]); + } + destructors.push(destructor); + destructors.push(ptr); + return ptr; + } + }; +} + +function __embind_register_struct_field( + structType, + fieldName, + fieldType, + getter, + setter, + memberPointerSize, + memberPointer +) { + structType = requireRegisteredType(structType, 'struct'); + fieldName = Pointer_stringify(fieldName); + fieldType = requireRegisteredType(fieldType, 'field "' + structType.name + '.' + fieldName + '"'); + getter = FUNCTION_TABLE[getter]; + setter = FUNCTION_TABLE[setter]; + memberPointer = copyMemberPointer(memberPointer, memberPointerSize); + + structType.fields[fieldName] = { + read: function(ptr) { + return fieldType.fromWireType(getter(ptr, memberPointer)); + }, + write: function(ptr, o) { + var destructors = []; + setter(ptr, memberPointer, fieldType.toWireType(destructors, o)); + runDestructors(destructors); + } + }; +} + +function __embind_register_class( + classType, + name, + destructor +) { + name = Pointer_stringify(name); + destructor = FUNCTION_TABLE[destructor]; + + var Handle = createNamedFunction(name, function(ptr) { + this.count = {value: 1}; + this.ptr = ptr; + }); + + Handle.prototype.clone = function() { + if (!this.ptr) { + throw new BindingError(classType.name + ' instance already deleted'); + } + + var clone = Object.create(Handle.prototype); + clone.count = this.count; + clone.ptr = this.ptr; + + clone.count.value += 1; + return clone; + }; + + Handle.prototype.move = function() { + var rv = this.clone(); + this.delete(); + return rv; + }; + + Handle.prototype['delete'] = function() { + if (!this.ptr) { + throw new BindingError(classType.name + ' instance already deleted'); + } + + this.count.value -= 1; + if (0 === this.count.value) { + destructor(this.ptr); + } + this.ptr = undefined; + }; + + var constructor = createNamedFunction(name, function() { + var body = constructor.body; + body.apply(this, arguments); + }); + constructor.prototype = Object.create(Handle.prototype); + + typeRegistry[classType] = { + name: name, + constructor: constructor, + Handle: Handle, + fromWireType: function(ptr) { + return new Handle(ptr); + }, + toWireType: function(destructors, o) { + return o.ptr; + } + }; + + Module[name] = constructor; +} + +function __embind_register_class_constructor( + classType, + argCount, + argTypes, + constructor +) { + classType = requireRegisteredType(classType, 'class'); + var humanName = 'constructor ' + classType.name; + argTypes = requireArgumentTypes(argCount, argTypes, humanName); + constructor = FUNCTION_TABLE[constructor]; + + classType.constructor.body = function() { + if (arguments.length !== argCount) { + throw new BindingError('emscripten binding ' + humanName + ' called with ' + arguments.length + ' arguments, expected ' + argCount); + } + var destructors = []; + var args = new Array(argCount); + for (var i = 0; i < argCount; ++i) { + args[i] = argTypes[i].toWireType(destructors, arguments[i]); + } + + var ptr = constructor.apply(null, args); + runDestructors(destructors); + classType.Handle.call(this, ptr); + }; +} + +function __embind_register_class_method( + classType, + methodName, + returnType, + argCount, + argTypes, + invoker, + memberFunctionSize, + memberFunction +) { + classType = requireRegisteredType(classType, 'class'); + methodName = Pointer_stringify(methodName); + var humanName = classType.name + '.' + methodName; + returnType = requireRegisteredType(returnType, 'method ' + humanName + ' return value'); + argTypes = requireArgumentTypes(argCount, argTypes, 'method ' + humanName); + invoker = FUNCTION_TABLE[invoker]; + memberFunction = copyMemberPointer(memberFunction, memberFunctionSize); + + classType.Handle.prototype[methodName] = function() { + if (!this.ptr) { + throw new BindingError('cannot call emscripten binding method ' + humanName + ' on deleted object'); + } + if (arguments.length !== argCount) { + throw new BindingError('emscripten binding method ' + humanName + ' called with ' + arguments.length + ' arguments, expected ' + argCount); + } + + var destructors = []; + var args = new Array(argCount + 2); + args[0] = this.ptr; + args[1] = memberFunction; + for (var i = 0; i < argCount; ++i) { + args[i + 2] = argTypes[i].toWireType(destructors, arguments[i]); + } + + var rv = returnType.fromWireType(invoker.apply(null, args)); + runDestructors(destructors); + return rv; + }; +} + +function __embind_register_class_classmethod( + classType, + methodName, + returnType, + argCount, + argTypes, + method +) { + classType = requireRegisteredType(classType, 'class'); + methodName = Pointer_stringify(methodName); + var humanName = classType.name + '.' + methodName; + returnType = requireRegisteredType(returnType, 'classmethod ' + humanName + ' return value'); + argTypes = requireArgumentTypes(argCount, argTypes, 'classmethod ' + humanName); + method = FUNCTION_TABLE[method]; + + classType.constructor[methodName] = function() { + if (arguments.length !== argCount) { + throw new BindingError('emscripten binding method ' + humanName + ' called with ' + arguments.length + ' arguments, expected ' + argCount); + } + + var destructors = []; + var args = new Array(argCount); + for (var i = 0; i < argCount; ++i) { + args[i] = argTypes[i].toWireType(destructors, arguments[i]); + } + + var rv = returnType.fromWireType(method.apply(null, args)); + runDestructors(destructors); + return rv; + }; +} + +function __embind_register_class_field( + classType, + fieldName, + fieldType, + getter, + setter, + memberPointerSize, + memberPointer +) { + classType = requireRegisteredType(classType, 'class'); + fieldName = Pointer_stringify(fieldName); + var humanName = classType.name + '.' + fieldName; + fieldType = requireRegisteredType(fieldType, 'field ' + humanName); + getter = FUNCTION_TABLE[getter]; + setter = FUNCTION_TABLE[setter]; + memberPointer = copyMemberPointer(memberPointer, memberPointerSize); + + Object.defineProperty(classType.Handle.prototype, fieldName, { + get: function() { + if (!this.ptr) { + throw new BindingError('cannot access emscripten binding field ' + humanName + ' on deleted object'); + } + return fieldType.fromWireType(getter(this.ptr, memberPointer)); + }, + set: function(v) { + if (!this.ptr) { + throw new BindingError('cannot modify emscripten binding field ' + humanName + ' on deleted object'); + } + var destructors = []; + setter(this.ptr, memberPointer, fieldType.toWireType(destructors, v)); + runDestructors(destructors); + }, + enumerable: true + }); +} + +function __embind_register_enum( + enumType, + name +) { + name = Pointer_stringify(name); + + function Enum() { + } + Enum.values = {}; + + typeRegistry[enumType] = { + name: name, + constructor: Enum, + toWireType: function(destructors, c) { + return c.value; + }, + fromWireType: function(c) { + return Enum.values[c]; + }, + }; + + Module[name] = Enum; +} + +function __embind_register_enum_value( + enumType, + name, + enumValue +) { + enumType = requireRegisteredType(enumType, 'enum'); + name = Pointer_stringify(name); + + var Enum = enumType.constructor; + + var Value = Object.create(enumType.constructor.prototype, { + value: {value: enumValue}, + constructor: {value: createNamedFunction(enumType.name + '_' + name, function() {})}, + }); + Enum.values[enumValue] = Value; + Enum[name] = Value; +} + +function __embind_register_interface( + interfaceType, + name, + constructor, + destructor +) { + name = Pointer_stringify(name); + constructor = FUNCTION_TABLE[constructor]; + destructor = FUNCTION_TABLE[destructor]; + + typeRegistry[interfaceType] = { + name: name, + toWireType: function(destructors, o) { + var handle = __emval_register(o); + var ptr = constructor(handle); + destructors.push(destructor); + destructors.push(ptr); + return ptr; + }, + }; +} diff --git a/src/embind/emval.js b/src/embind/emval.js new file mode 100644 index 00000000..9574ab37 --- /dev/null +++ b/src/embind/emval.js @@ -0,0 +1,111 @@ +/*global Module*/ +/*global HEAP32*/ +/*global Pointer_stringify, writeStringToMemory*/ +/*global requireRegisteredType*/ + +var _emval_handle_array = []; +var _emval_free_list = []; + +// Public JS API + +/** @expose */ +Module.count_emval_handles = function() { + return _emval_handle_array.length; +}; + +// Private C++ API + +function __emval_register(value) { + var handle = _emval_free_list.length ? + _emval_free_list.pop() : + _emval_handle_array.length; + _emval_handle_array[handle] = {refcount: 1, value: value}; + return handle; +} + +function __emval_incref(handle) { + _emval_handle_array[handle].refcount += 1; +} + +function __emval_decref(handle) { + if (0 === --_emval_handle_array[handle].refcount) { + delete _emval_handle_array[handle]; + _emval_free_list.push(handle); + + var actual_length = _emval_handle_array.length; + while (actual_length > 0 && _emval_handle_array[actual_length - 1] === undefined) { + --actual_length; + } + _emval_handle_array.length = actual_length; + } +} + +function __emval_new_object() { + return __emval_register({}); +} + +function __emval_new_long(value) { + return __emval_register(value); +} + +function __emval_new_cstring(str) { + return __emval_register(Pointer_stringify(str)); +} + +function __emval_get_property(handle, k) { + k = Pointer_stringify(k); + return __emval_register(_emval_handle_array[handle].value[k]); +} + +function __emval_get_property_by_long(handle, k) { + return __emval_register(_emval_handle_array[handle].value[k]); +} + +function __emval_get_property_by_unsigned_long(handle, k) { + return __emval_register(_emval_handle_array[handle].value[k]); +} + +function __emval_set_property(handle, k, value) { + k = Pointer_stringify(k); + _emval_handle_array[handle].value[k] = _emval_handle_array[value].value; +} + +function __emval_set_property_by_int(handle, k, value) { + _emval_handle_array[handle].value[k] = _emval_handle_array[value].value; +} + +function __emval_as(handle, returnType) { + returnType = requireRegisteredType(returnType, 'emval::as'); + var destructors = []; + // caller owns destructing + return returnType.toWireType(destructors, _emval_handle_array[handle].value); +} + +function __emval_call(handle, argCount, argTypes) { + var args = Array.prototype.slice.call(arguments, 3); + var fn = _emval_handle_array[handle].value; + var a = new Array(argCount); + for (var i = 0; i < argCount; ++i) { + var argType = requireRegisteredType( + HEAP32[(argTypes >> 2) + i], + "parameter " + i); + a[i] = argType.fromWireType(args[i]); + } + var rv = fn.apply(undefined, a); + return __emval_register(rv); +} + +function __emval_call_method(handle, name, argCount, argTypes) { + name = Pointer_stringify(name); + var args = Array.prototype.slice.call(arguments, 4); + var obj = _emval_handle_array[handle].value; + var a = new Array(argCount); + for (var i = 0; i < argCount; ++i) { + var argType = requireRegisteredType( + HEAP32[(argTypes >> 2) + i], + "parameter " + i); + a[i] = argType.fromWireType(args[i]); + } + var rv = obj[name].apply(obj, a); + return __emval_register(rv); +} diff --git a/system/include/emscripten/bind.h b/system/include/emscripten/bind.h new file mode 100644 index 00000000..8f56ff87 --- /dev/null +++ b/system/include/emscripten/bind.h @@ -0,0 +1,645 @@ +#pragma once + +#include <stddef.h> +#include <string> +#include <type_traits> +#include <emscripten/val.h> +#include <emscripten/wire.h> + +namespace emscripten { + namespace internal { + typedef void (*GenericFunction)(); + typedef long GenericEnumValue; + + // Implemented in JavaScript. Don't call these directly. + extern "C" { + void _embind_fatal_error( + const char* name, + const char* payload) __attribute__((noreturn)); + + void _embind_register_void( + TypeID voidType, + const char* name); + + void _embind_register_bool( + TypeID boolType, + const char* name, + bool trueValue, + bool falseValue); + + void _embind_register_integer( + TypeID integerType, + const char* name); + + void _embind_register_float( + TypeID floatType, + const char* name); + + void _embind_register_cstring( + TypeID stringType, + const char* name); + + void _embind_register_emval( + TypeID emvalType, + const char* name); + + void _embind_register_function( + const char* name, + TypeID returnType, + unsigned argCount, + TypeID argTypes[], + GenericFunction invoker, + GenericFunction function); + + void _embind_register_tuple( + TypeID tupleType, + const char* name, + GenericFunction constructor, + GenericFunction destructor); + + void _embind_register_tuple_element( + TypeID tupleType, + TypeID elementType, + GenericFunction getter, + GenericFunction setter, + size_t memberPointerSize, + void* memberPointer); + + void _embind_register_tuple_element_accessor( + TypeID tupleType, + TypeID elementType, + GenericFunction staticGetter, + size_t getterSize, + void* getter, + GenericFunction staticSetter, + size_t setterSize, + void* setter); + + void _embind_register_struct( + TypeID structType, + const char* name, + GenericFunction constructor, + GenericFunction destructor); + + void _embind_register_struct_field( + TypeID structType, + const char* name, + TypeID fieldType, + GenericFunction getter, + GenericFunction setter, + size_t memberPointerSize, + void* memberPointer); + + void _embind_register_class( + TypeID classType, + const char* className, + GenericFunction destructor); + + void _embind_register_class_constructor( + TypeID classType, + unsigned argCount, + TypeID argTypes[], + GenericFunction constructor); + + void _embind_register_class_method( + TypeID classType, + const char* methodName, + TypeID returnType, + unsigned argCount, + TypeID argTypes[], + GenericFunction invoker, + size_t memberFunctionSize, + void* memberFunction); + + void _embind_register_class_field( + TypeID classType, + const char* fieldName, + TypeID fieldType, + GenericFunction getter, + GenericFunction setter, + size_t memberPointerSize, + void* memberPointer); + + void _embind_register_class_classmethod( + TypeID classType, + const char* methodName, + TypeID returnType, + unsigned argCount, + TypeID argTypes[], + GenericFunction method); + + void _embind_register_enum( + TypeID enumType, + const char* name); + + void _embind_register_enum_value( + TypeID enumType, + const char* valueName, + GenericEnumValue value); + + void _embind_register_interface( + TypeID interfaceType, + const char* name, + GenericFunction constructor, + GenericFunction destructor); + } + + extern void registerStandardTypes(); + + class BindingsDefinition { + public: + template<typename Function> + BindingsDefinition(Function fn) { + fn(); + } + }; + } +} + +namespace emscripten { + namespace internal { + template<typename ReturnType, typename... Args> + struct Invoker { + static typename internal::BindingType<ReturnType>::WireType invoke( + ReturnType (fn)(Args...), + typename internal::BindingType<Args>::WireType... args + ) { + return internal::BindingType<ReturnType>::toWireType( + fn( + internal::BindingType<Args>::fromWireType(args)... + ) + ); + } + }; + + template<typename... Args> + struct Invoker<void, Args...> { + static void invoke( + void (fn)(Args...), + typename internal::BindingType<Args>::WireType... args + ) { + return fn( + internal::BindingType<Args>::fromWireType(args)... + ); + } + }; + } + + template<typename ReturnType, typename... Args> + void function(const char* name, ReturnType (fn)(Args...)) { + internal::registerStandardTypes(); + + internal::ArgTypeList<Args...> args; + internal::_embind_register_function( + name, + internal::getTypeID<ReturnType>(), + args.count, + args.types, + reinterpret_cast<internal::GenericFunction>(&internal::Invoker<ReturnType, Args...>::invoke), + reinterpret_cast<internal::GenericFunction>(fn)); + } + + namespace internal { + template<typename ClassType, typename... Args> + ClassType* raw_constructor( + typename internal::BindingType<Args>::WireType... args + ) { + return new ClassType( + internal::BindingType<Args>::fromWireType(args)... + ); + } + + template<typename ClassType> + void raw_destructor(ClassType* ptr) { + delete ptr; + } + + template<typename ClassType, typename ReturnType, typename... Args> + struct MethodInvoker { + typedef ReturnType (ClassType::*MemberPointer)(Args...); + typename internal::BindingType<ReturnType>::WireType invoke( + ClassType* ptr, + const MemberPointer& method, + typename internal::BindingType<Args>::WireType... args + ) { + return internal::BindingType<ReturnType>::toWireType( + (ptr->*method)( + internal::BindingType<Args>::fromWireType(args)... + ) + ); + } + }; + + template<typename ClassType, typename... Args> + struct MethodInvoker<ClassType, void, Args...> { + typedef void (ClassType::*MemberPointer)(Args...); + static void invoke( + ClassType* ptr, + const MemberPointer& method, + typename internal::BindingType<Args>::WireType... args + ) { + return (ptr->*method)( + internal::BindingType<Args>::fromWireType(args)... + ); + } + }; + + template<typename ClassType, typename ReturnType, typename... Args> + struct ConstMethodInvoker { + typedef ReturnType (ClassType::*MemberPointer)(Args...) const; + static typename internal::BindingType<ReturnType>::WireType invoke( + const ClassType* ptr, + const MemberPointer& method, + typename internal::BindingType<Args>::WireType... args + ) { + return internal::BindingType<ReturnType>::toWireType( + (ptr->*method)( + internal::BindingType<Args>::fromWireType(args)... + ) + ); + } + }; + + template<typename ClassType, typename... Args> + struct ConstMethodInvoker<ClassType, void, Args...> { + typedef void (ClassType::*MemberPointer)(Args...) const; + static void invoke( + const ClassType* ptr, + const MemberPointer& method, + typename internal::BindingType<Args>::WireType... args + ) { + return (ptr->*method)( + internal::BindingType<Args>::fromWireType(args)... + ); + } + }; + + template<typename ClassType, typename FieldType> + struct FieldAccess { + typedef FieldType ClassType::*MemberPointer; + typedef internal::BindingType<FieldType> FieldBinding; + typedef typename FieldBinding::WireType WireType; + + static WireType get( + ClassType& ptr, + const MemberPointer& field + ) { + return FieldBinding::toWireType(ptr.*field); + } + + static void set( + ClassType& ptr, + const MemberPointer& field, + WireType value + ) { + ptr.*field = FieldBinding::fromWireType(value); + } + + template<typename Getter> + static WireType propertyGet( + ClassType& ptr, + const Getter& getter + ) { + return FieldBinding::toWireType(getter(ptr)); + } + + template<typename Setter> + static void propertySet( + ClassType& ptr, + const Setter& setter, + WireType value + ) { + setter(ptr, FieldBinding::fromWireType(value)); + } + }; + } + + template<typename ClassType> + class value_tuple { + public: + value_tuple(const char* name) { + internal::registerStandardTypes(); + internal::_embind_register_tuple( + internal::getTypeID<ClassType>(), + name, + reinterpret_cast<internal::GenericFunction>(&internal::raw_constructor<ClassType>), + reinterpret_cast<internal::GenericFunction>(&internal::raw_destructor<ClassType>)); + } + + template<typename ElementType> + value_tuple& element(ElementType ClassType::*field) { + internal::_embind_register_tuple_element( + internal::getTypeID<ClassType>(), + internal::getTypeID<ElementType>(), + reinterpret_cast<internal::GenericFunction>(&internal::FieldAccess<ClassType, ElementType>::get), + reinterpret_cast<internal::GenericFunction>(&internal::FieldAccess<ClassType, ElementType>::set), + sizeof(field), + &field); + + return *this; + } + + template<typename ElementType> + value_tuple& element(ElementType (*getter)(const ClassType&), void (*setter)(ClassType&, ElementType)) { + internal::_embind_register_tuple_element_accessor( + internal::getTypeID<ClassType>(), + internal::getTypeID<ElementType>(), + reinterpret_cast<internal::GenericFunction>(&internal::FieldAccess<ClassType, ElementType>::template propertyGet<ElementType(const ClassType&)>), + sizeof(getter), + &getter, + reinterpret_cast<internal::GenericFunction>(&internal::FieldAccess<ClassType, ElementType>::template propertySet<void(ClassType&, ElementType)>), + sizeof(setter), + &setter); + return *this; + } + + template<typename ElementType> + value_tuple& element(ElementType (*getter)(const ClassType&), void (*setter)(ClassType&, const ElementType&)) { + internal::_embind_register_tuple_element_accessor( + internal::getTypeID<ClassType>(), + internal::getTypeID<ElementType>(), + reinterpret_cast<internal::GenericFunction>(&internal::FieldAccess<ClassType, ElementType>::template propertyGet<ElementType(const ClassType&)>), + sizeof(getter), + &getter, + reinterpret_cast<internal::GenericFunction>(&internal::FieldAccess<ClassType, ElementType>::template propertySet<void(ClassType&, ElementType)>), + sizeof(setter), + &setter); + return *this; + } + + template<typename ElementType> + value_tuple& element(ElementType (*getter)(const ClassType&), void (*setter)(ClassType&, const ElementType&&)) { + internal::_embind_register_tuple_element_accessor( + internal::getTypeID<ClassType>(), + internal::getTypeID<ElementType>(), + reinterpret_cast<internal::GenericFunction>(&internal::FieldAccess<ClassType, ElementType>::template propertyGet<ElementType(const ClassType&)>), + sizeof(getter), + &getter, + reinterpret_cast<internal::GenericFunction>(&internal::FieldAccess<ClassType, ElementType>::template propertySet<void(ClassType&, ElementType)>), + sizeof(setter), + &setter); + return *this; + } + + template<typename ElementType> + value_tuple& element(ElementType (*getter)(const ClassType&), void (*setter)(ClassType&, ElementType&)) { + internal::_embind_register_tuple_element_accessor( + internal::getTypeID<ClassType>(), + internal::getTypeID<ElementType>(), + reinterpret_cast<internal::GenericFunction>(&internal::FieldAccess<ClassType, ElementType>::template propertyGet<ElementType(const ClassType&)>), + sizeof(getter), + &getter, + reinterpret_cast<internal::GenericFunction>(&internal::FieldAccess<ClassType, ElementType>::template propertySet<void(ClassType&, ElementType)>), + sizeof(setter), + &setter); + return *this; + } + }; + + template<typename ClassType> + class value_struct { + public: + value_struct(const char* name) { + internal::registerStandardTypes(); + internal::_embind_register_struct( + internal::getTypeID<ClassType>(), + name, + reinterpret_cast<internal::GenericFunction>(&internal::raw_constructor<ClassType>), + reinterpret_cast<internal::GenericFunction>(&internal::raw_destructor<ClassType>)); + } + + template<typename FieldType> + value_struct& field(const char* fieldName, FieldType ClassType::*field) { + internal::_embind_register_struct_field( + internal::getTypeID<ClassType>(), + fieldName, + internal::getTypeID<FieldType>(), + reinterpret_cast<internal::GenericFunction>(&internal::FieldAccess<ClassType, FieldType>::get), + reinterpret_cast<internal::GenericFunction>(&internal::FieldAccess<ClassType, FieldType>::set), + sizeof(field), + &field); + + return *this; + } + }; + + // TODO: support class definitions without constructors. + // TODO: support external class constructors + template<typename ClassType> + class class_ { + public: + class_(const char* name) { + internal::registerStandardTypes(); + internal::_embind_register_class( + internal::getTypeID<ClassType>(), + name, + reinterpret_cast<internal::GenericFunction>(&internal::raw_destructor<ClassType>)); + } + + template<typename... ConstructorArgs> + class_& constructor() { + internal::ArgTypeList<ConstructorArgs...> args; + internal::_embind_register_class_constructor( + internal::getTypeID<ClassType>(), + args.count, + args.types, + reinterpret_cast<internal::GenericFunction>(&internal::raw_constructor<ClassType, ConstructorArgs...>)); + } + + template<typename ReturnType, typename... Args> + class_& method(const char* methodName, ReturnType (ClassType::*memberFunction)(Args...)) { + internal::ArgTypeList<Args...> args; + internal::_embind_register_class_method( + internal::getTypeID<ClassType>(), + methodName, + internal::getTypeID<ReturnType>(), + args.count, + args.types, + reinterpret_cast<internal::GenericFunction>(&internal::MethodInvoker<ClassType, ReturnType, Args...>::invoke), + sizeof(memberFunction), + &memberFunction); + return *this; + } + + template<typename ReturnType, typename... Args> + class_& method(const char* methodName, ReturnType (ClassType::*memberFunction)(Args...) const) { + internal::ArgTypeList<Args...> args; + internal::_embind_register_class_method( + internal::getTypeID<ClassType>(), + methodName, + internal::getTypeID<ReturnType>(), + args.count, + args.types, + reinterpret_cast<internal::GenericFunction>(&internal::ConstMethodInvoker<ClassType, ReturnType, Args...>::invoke), + sizeof(memberFunction), + &memberFunction); + return *this; + } + + template<typename FieldType> + class_& field(const char* fieldName, FieldType ClassType::*field) { + internal::_embind_register_class_field( + internal::getTypeID<ClassType>(), + fieldName, + internal::getTypeID<FieldType>(), + reinterpret_cast<internal::GenericFunction>(&internal::FieldAccess<ClassType, FieldType>::get), + reinterpret_cast<internal::GenericFunction>(&internal::FieldAccess<ClassType, FieldType>::set), + sizeof(field), + &field); + return *this; + } + + template<typename ReturnType, typename... Args> + class_& classmethod(const char* methodName, ReturnType (*classMethod)(Args...)) { + internal::ArgTypeList<Args...> args; + internal::_embind_register_class_classmethod( + internal::getTypeID<ClassType>(), + methodName, + internal::getTypeID<ReturnType>(), + args.count, + args.types, + reinterpret_cast<internal::GenericFunction>(classMethod)); + return *this; + } + }; + + template<typename EnumType> + class enum_ { + public: + enum_(const char* name) { + _embind_register_enum( + internal::getTypeID<EnumType>(), + name); + } + + enum_& value(const char* name, EnumType value) { + // TODO: there's still an issue here. + // if EnumType is an unsigned long, then JS may receive it as a signed long + static_assert(sizeof(value) <= sizeof(internal::GenericEnumValue), "enum type must fit in a GenericEnumValue"); + + _embind_register_enum_value( + internal::getTypeID<EnumType>(), + name, + static_cast<internal::GenericEnumValue>(value)); + return *this; + } + }; + + namespace internal { + template<typename T> + class optional { + public: + optional() + : initialized(false) + {} + + ~optional() { + if (initialized) { + get()->~T(); + } + } + + optional(const optional&) = delete; + + T& operator*() { + assert(initialized); + return *get(); + } + + explicit operator bool() const { + return initialized; + } + + optional& operator=(const T& v) { + if (initialized) { + get()->~T(); + } + new(get()) T(v); + initialized = true; + } + + private: + T* get() { + return reinterpret_cast<T*>(&data); + } + + bool initialized; + typename std::aligned_storage<sizeof(T)>::type data; + }; + } + + template<typename InterfaceType> + class wrapper : public InterfaceType { + public: + // Not necessary in any example so far, but appeases a compiler warning. + virtual ~wrapper() {} + + typedef InterfaceType interface; + + void initialize(internal::EM_VAL handle) { + if (jsobj) { + internal::_embind_fatal_error( + "Cannot initialize interface wrapper twice", + typeid(InterfaceType).name()); + } + jsobj = val::take_ownership(handle); + } + + template<typename ReturnType, typename... Args> + ReturnType call(const char* name, Args... args) { + assertInitialized(); + return Caller<ReturnType, Args...>::call(*jsobj, name, args...); + } + + private: + // this class only exists because you can't partially specialize function templates + template<typename ReturnType, typename... Args> + struct Caller { + static ReturnType call(val& v, const char* name, Args... args) { + return v.call(name, args...).template as<ReturnType>(); + } + }; + + template<typename... Args> + struct Caller<void, Args...> { + static void call(val& v, const char* name, Args... args) { + v.call(name, args...); + } + }; + + void assertInitialized() { + if (!jsobj) { + internal::_embind_fatal_error( + "Cannot invoke call on uninitialized interface wrapper.", + typeid(InterfaceType).name()); + } + } + + internal::optional<val> jsobj; + }; + + namespace internal { + template<typename WrapperType> + WrapperType* create_interface_wrapper(EM_VAL e) { + WrapperType* p = new WrapperType; + p->initialize(e); + return p; + } + } + + template<typename WrapperType> + class interface { + public: + typedef typename WrapperType::interface InterfaceType; + + interface(const char* name) { + _embind_register_interface( + internal::getTypeID<InterfaceType>(), + name, + reinterpret_cast<internal::GenericFunction>(&internal::create_interface_wrapper<WrapperType>), + reinterpret_cast<internal::GenericFunction>(&internal::raw_destructor<WrapperType>)); + } + }; +} + +#define EMSCRIPTEN_BINDINGS(fn) static emscripten::internal::BindingsDefinition anon_symbol(fn); diff --git a/system/include/emscripten/val.h b/system/include/emscripten/val.h new file mode 100644 index 00000000..96db9326 --- /dev/null +++ b/system/include/emscripten/val.h @@ -0,0 +1,177 @@ +#pragma once + +#include <stdint.h> // uintptr_t +#include <emscripten/wire.h> + +namespace emscripten { + namespace internal { + // Implemented in JavaScript. Don't call these directly. + extern "C" { + typedef struct _EM_VAL* EM_VAL; + + void _emval_incref(EM_VAL value); + void _emval_decref(EM_VAL value); + EM_VAL _emval_new_object(); + EM_VAL _emval_new_long(long value); + EM_VAL _emval_new_cstring(const char* str); + EM_VAL _emval_get_property(EM_VAL object, const char* key); + EM_VAL _emval_get_property_by_long(EM_VAL object, long key); + EM_VAL _emval_get_property_by_unsigned_long(EM_VAL object, unsigned long key); + void _emval_set_property(EM_VAL object, const char* key, EM_VAL value); + void _emval_set_property_by_int(EM_VAL object, long key, EM_VAL value); + void _emval_as(EM_VAL value, emscripten::internal::TypeID returnType); + EM_VAL _emval_call( + EM_VAL value, + unsigned argCount, + internal::TypeID argTypes[] + /*, ... */); + EM_VAL _emval_call_method( + EM_VAL value, + const char* methodName, + unsigned argCount, + internal::TypeID argTypes[] + /*, ... */); + } + } + + class val { + public: + static val object() { + return val(internal::_emval_new_object()); + }; + + static val take_ownership(internal::EM_VAL e) { + return val(e); + } + + explicit val(long l) + : handle(internal::_emval_new_long(l)) + {} + + explicit val(const char* str) + : handle(internal::_emval_new_cstring(str)) + {} + + val() = delete; + + val(const val& v) + : handle(v.handle) + { + internal::_emval_incref(handle); + } + + ~val() { + internal::_emval_decref(handle); + } + + val& operator=(const val& v) { + internal::_emval_incref(v.handle); + internal::_emval_decref(handle); + handle = v.handle; + return *this; + } + + val get(const char* key) const { + return val(internal::_emval_get_property(handle, key)); + } + + val get(int key) const { + return get(long(key)); + } + + val get(unsigned int key) const { + typedef unsigned long T; + return get(T(key)); + } + + val get(long key) const { + return val(internal::_emval_get_property_by_long(handle, key)); + } + + val get(unsigned long key) const { + return val(internal::_emval_get_property_by_unsigned_long(handle, key)); + } + + void set(const char* key, val v) { + internal::_emval_set_property(handle, key, v.handle); + } + + void set(long key, val v) { + internal::_emval_set_property_by_int(handle, key, v.handle); + } + + template<typename ...Args> + val operator()(Args... args) { + internal::ArgTypeList<Args...> argList; + typedef internal::EM_VAL (*TypedCall)( + internal::EM_VAL, + unsigned, + internal::TypeID argTypes[], + typename internal::BindingType<Args>::WireType...); + TypedCall typedCall = reinterpret_cast<TypedCall>(&internal::_emval_call); + return val( + typedCall( + handle, + argList.count, + argList.types, + internal::toWireType(args)...)); + } + + template<typename ...Args> + val call(const char* name, Args... args) { + internal::ArgTypeList<Args...> argList; + typedef internal::EM_VAL (*TypedCall)( + internal::EM_VAL, + const char* name, + unsigned, + internal::TypeID argTypes[], + typename internal::BindingType<Args>::WireType...); + TypedCall typedCall = reinterpret_cast<TypedCall>(&internal::_emval_call_method); + return val( + typedCall( + handle, + name, + argList.count, + argList.types, + internal::toWireType(args)...)); + } + + template<typename T> + T as() const { + typedef internal::BindingType<T> BT; + + typedef typename BT::WireType (*TypedAs)( + internal::EM_VAL value, + emscripten::internal::TypeID returnType); + TypedAs typedAs = reinterpret_cast<TypedAs>(&internal::_emval_as); + + typename BT::WireType wt = typedAs(handle, internal::getTypeID<T>()); + internal::WireDeleter<T> deleter(wt); + return BT::fromWireType(wt); + } + + private: + // takes ownership, assumes handle already incref'd + explicit val(internal::EM_VAL handle) + : handle(handle) + {} + + internal::EM_VAL handle; + + friend struct internal::BindingType<val>; + }; + + namespace internal { + template<> + struct BindingType<val> { + typedef internal::EM_VAL WireType; + static WireType toWireType(val v) { + _emval_incref(v.handle); + return v.handle; + } + static val fromWireType(WireType v) { + return val(v); + } + }; + } +} diff --git a/system/include/emscripten/wire.h b/system/include/emscripten/wire.h new file mode 100644 index 00000000..722bf4b8 --- /dev/null +++ b/system/include/emscripten/wire.h @@ -0,0 +1,223 @@ +#pragma once + +// A value moving between JavaScript and C++ has three representations: +// - The original JS value: a String +// - The native on-the-wire value: a stack-allocated char*, say +// - The C++ value: std::string +// +// We'll call the on-the-wire type WireType. + +namespace emscripten { + namespace internal { + typedef const struct _TypeID* TypeID; + + // This implementation is technically not legal, as it's not + // required that two calls to typeid produce the same exact + // std::type_info instance. That said, it's likely to work. + // Should it not work in the future: replace TypeID with + // an int, and store all TypeInfo we see in a map, allocating + // new TypeIDs as we add new items to the map. + template<typename T> + inline TypeID getTypeID() { + return reinterpret_cast<TypeID>(&typeid(T)); + } + + // count<> + + template<typename... Args> + struct count; + + template<> + struct count<> { + enum { value = 0 }; + }; + + template<typename T, typename... Args> + struct count<T, Args...> { + enum { value = 1 + count<Args...>::value }; + }; + + // ArgTypeList<> + + template<typename... Args> + struct ArgTypes; + + template<> + struct ArgTypes<> { + static void fill(TypeID* argTypes) { + } + }; + + template<typename T, typename... Args> + struct ArgTypes<T, Args...> { + static void fill(TypeID* argTypes) { + *argTypes = getTypeID<T>(); + return ArgTypes<Args...>::fill(argTypes + 1); + } + }; + + template<typename... Args> + struct ArgTypeList { + enum { args_count = count<Args...>::value }; + + ArgTypeList() { + count = args_count; + ArgTypes<Args...>::fill(types); + } + + unsigned count; + TypeID types[args_count]; + }; + + // BindingType<T> + + template<typename T> + struct BindingType; + +#define EMSCRIPTEN_DEFINE_NATIVE_BINDING_TYPE(type) \ + template<> \ + struct BindingType<type> { \ + typedef type WireType; \ + \ + constexpr static WireType toWireType(type v) { \ + return v; \ + } \ + constexpr static type fromWireType(WireType v) { \ + return v; \ + } \ + static void destroy(WireType) { \ + } \ + } + + EMSCRIPTEN_DEFINE_NATIVE_BINDING_TYPE(char); + EMSCRIPTEN_DEFINE_NATIVE_BINDING_TYPE(signed char); + EMSCRIPTEN_DEFINE_NATIVE_BINDING_TYPE(unsigned char); + EMSCRIPTEN_DEFINE_NATIVE_BINDING_TYPE(signed short); + EMSCRIPTEN_DEFINE_NATIVE_BINDING_TYPE(unsigned short); + EMSCRIPTEN_DEFINE_NATIVE_BINDING_TYPE(signed int); + EMSCRIPTEN_DEFINE_NATIVE_BINDING_TYPE(unsigned int); + EMSCRIPTEN_DEFINE_NATIVE_BINDING_TYPE(signed long); + EMSCRIPTEN_DEFINE_NATIVE_BINDING_TYPE(unsigned long); + EMSCRIPTEN_DEFINE_NATIVE_BINDING_TYPE(float); + EMSCRIPTEN_DEFINE_NATIVE_BINDING_TYPE(double); + + template<> + struct BindingType<void> { + }; + + template<> + struct BindingType<bool> { + typedef bool WireType; + static WireType toWireType(bool b) { + return b; + } + static bool fromWireType(WireType wt) { + return wt; + } + static void destroy(WireType) { + } + }; + + template<> + struct BindingType<std::string> { + typedef char* WireType; + static WireType toWireType(std::string v) { + return strdup(v.c_str()); + } + static std::string fromWireType(char* v) { + return std::string(v); + } + }; + + template<> + struct BindingType<const std::string&> { + typedef char* WireType; + static WireType toWireType(std::string v) { + return strdup(v.c_str()); + } + static std::string fromWireType(char* v) { + return std::string(v); + } + }; + + template<typename Enum> + struct EnumBindingType { + typedef Enum WireType; + + static WireType toWireType(Enum v) { + return v; + } + static Enum fromWireType(WireType v) { + return v; + } + }; + + template<typename T> + struct GenericBindingType { + typedef typename std::remove_reference<T>::type ActualT; + typedef ActualT* WireType; + + struct Marshaller { + explicit Marshaller(WireType wt) + : wireType(wt) + {} + + Marshaller(Marshaller&& wt) + : wireType(wt.wireType) + { + wt.wireType = 0; + } + + operator ActualT&() const { + return *wireType; + } + + private: + Marshaller() = delete; + Marshaller(const Marshaller&) = delete; + ActualT* wireType; + }; + + static WireType toWireType(T v) { + return new T(v); + } + + static Marshaller fromWireType(WireType p) { + return Marshaller(p); + } + + static void destroy(WireType p) { + delete p; + } + }; + + template<typename T> + struct WireDeleter { + typedef typename BindingType<T>::WireType WireType; + + WireDeleter(WireType wt) + : wt(wt) + {} + + ~WireDeleter() { + BindingType<T>::destroy(wt); + } + + WireType wt; + }; + + // catch-all generic binding + template<typename T> + struct BindingType : std::conditional< + std::is_enum<T>::value, + EnumBindingType<T>, + GenericBindingType<T>>::type + {}; + + template<typename T> + auto toWireType(const T& v) -> typename BindingType<T>::WireType { + return BindingType<T>::toWireType(v); + } + + } +} diff --git a/system/lib/embind/bind.cpp b/system/lib/embind/bind.cpp new file mode 100755 index 00000000..b63a86aa --- /dev/null +++ b/system/lib/embind/bind.cpp @@ -0,0 +1,34 @@ +#include <emscripten/bind.h>
+
+using namespace emscripten;
+
+namespace emscripten {
+ namespace internal {
+ void registerStandardTypes() {
+ static bool first = true;
+ if (first) {
+ first = false;
+
+ _embind_register_void(getTypeID<void>(), "void");
+
+ _embind_register_bool(getTypeID<bool>(), "bool", true, false);
+
+ _embind_register_integer(getTypeID<char>(), "char");
+ _embind_register_integer(getTypeID<signed char>(), "signed char");
+ _embind_register_integer(getTypeID<unsigned char>(), "unsigned char");
+ _embind_register_integer(getTypeID<signed short>(), "short");
+ _embind_register_integer(getTypeID<unsigned short>(), "unsigned short");
+ _embind_register_integer(getTypeID<signed int>(), "int");
+ _embind_register_integer(getTypeID<unsigned int>(), "unsigned int");
+ _embind_register_integer(getTypeID<signed long>(), "long");
+ _embind_register_integer(getTypeID<unsigned long>(), "unsigned long");
+
+ _embind_register_float(getTypeID<float>(), "float");
+ _embind_register_float(getTypeID<double>(), "double");
+
+ _embind_register_cstring(getTypeID<std::string>(), "std::string");
+ _embind_register_emval(getTypeID<val>(), "emscripten::val");
+ }
+ }
+ }
+}
diff --git a/tests/embind/embind_test.cpp b/tests/embind/embind_test.cpp new file mode 100644 index 00000000..e7b4d985 --- /dev/null +++ b/tests/embind/embind_test.cpp @@ -0,0 +1,335 @@ +#include <string>
+#include <malloc.h>
+#include <emscripten/bind.h>
+
+using namespace emscripten;
+
+val emval_test_mallinfo() {
+ const auto& i = mallinfo();
+ val rv(val::object());
+ rv.set("arena", val(i.arena));
+ rv.set("ordblks", val(i.ordblks));
+ rv.set("smblks", val(i.smblks));
+ rv.set("hblks", val(i.hblks));
+ rv.set("usmblks", val(i.usmblks));
+ rv.set("fsmblks", val(i.fsmblks));
+ rv.set("uordblks", val(i.uordblks));
+ rv.set("fordblks", val(i.fordblks));
+ rv.set("keepcost", val(i.keepcost));
+ return rv;
+}
+
+val emval_test_new_integer() {
+ return val(15);
+}
+
+val emval_test_new_string() {
+ return val("Hello everyone");
+}
+
+val emval_test_new_object() {
+ val rv(val::object());
+ rv.set("foo", val("bar"));
+ rv.set("baz", val(1));
+ return rv;
+}
+
+unsigned emval_test_passthrough_unsigned(unsigned v) {
+ return v;
+}
+
+val emval_test_passthrough(val v) {
+ return v;
+}
+
+void emval_test_return_void() {
+}
+
+bool emval_test_not(bool b) {
+ return !b;
+}
+
+unsigned emval_test_as_unsigned(val v) {
+ return v.as<unsigned>();
+}
+
+unsigned emval_test_get_length(val v) {
+ return v.get("length").as<unsigned>();
+}
+
+double emval_test_add(char c, signed char sc, unsigned char uc, signed short ss, unsigned short us, signed int si, unsigned int ui, signed long sl, unsigned long ul, float f, double d) {
+ return c + sc + uc + ss + us + si + ui + sl + ul + f + d;
+}
+
+unsigned emval_test_sum(val v) {
+ unsigned length = v.get("length").as<unsigned>();
+ double rv = 0;
+ for (unsigned i = 0; i < length; ++i) {
+ rv += v.get(i).as<double>();
+ }
+ return rv;
+}
+
+std::string emval_test_take_and_return_const_char_star(const char* str) {
+ return str;
+}
+
+std::string emval_test_take_and_return_std_string(std::string str) {
+ return str;
+}
+
+std::string emval_test_take_and_return_std_string_const_ref(const std::string& str) {
+ return str;
+}
+
+class ValHolder {
+public:
+ ValHolder(val v)
+ : v(v)
+ {}
+
+ val getVal() const {
+ return v;
+ }
+
+ void setVal(val v) {
+ this->v = v;
+ }
+
+ static int some_class_method(int i) {
+ return i;
+ }
+
+private:
+ val v;
+};
+
+ValHolder emval_test_return_ValHolder() {
+ return val::object();
+}
+
+void emval_test_set_ValHolder_to_empty_object(ValHolder& vh) {
+ vh.setVal(val::object());
+}
+
+class StringHolder {
+public:
+ StringHolder(const std::string& s)
+ : str(s)
+ {}
+
+ void set(const std::string& s) {
+ str = s;
+ }
+ std::string get() const {
+ return str;
+ }
+
+private:
+ std::string str;
+};
+
+struct TupleVector {
+ float x, y, z;
+};
+
+float readTupleVectorZ(const TupleVector& v) {
+ return v.z;
+}
+
+void writeTupleVectorZ(TupleVector& v, float z) {
+ v.z = z;
+}
+
+struct TupleVectorTuple {
+ TupleVector v;
+};
+
+TupleVector emval_test_return_TupleVector() {
+ TupleVector cv;
+ cv.x = 1;
+ cv.y = 2;
+ cv.z = 3;
+ return cv;
+}
+
+TupleVector emval_test_take_and_return_TupleVector(TupleVector v) {
+ return v;
+}
+
+TupleVectorTuple emval_test_return_TupleVectorTuple() {
+ TupleVectorTuple cvt;
+ cvt.v = emval_test_return_TupleVector();
+ return cvt;
+}
+
+struct StructVector {
+ float x, y, z;
+};
+
+StructVector emval_test_return_StructVector() {
+ StructVector v;
+ v.x = 1;
+ v.y = 2;
+ v.z = 3;
+ return v;
+}
+
+StructVector emval_test_take_and_return_StructVector(StructVector v) {
+ return v;
+}
+
+struct CustomStruct {
+ CustomStruct()
+ : field(10)
+ {}
+ int field;
+};
+
+struct TupleInStruct {
+ TupleVector field;
+};
+
+TupleInStruct emval_test_take_and_return_TupleInStruct(TupleInStruct cs) {
+ return cs;
+}
+
+enum Enum { ONE, TWO };
+
+Enum emval_test_take_and_return_Enum(Enum e) {
+ return e;
+}
+
+enum class EnumClass { ONE, TWO };
+
+EnumClass emval_test_take_and_return_EnumClass(EnumClass e) {
+ return e;
+}
+
+class Interface {
+public:
+ virtual int method() = 0;
+ virtual TupleInStruct method2(const TupleInStruct& arg1, float arg2) = 0;
+ virtual void method3() = 0;
+};
+
+int emval_test_call_method(Interface& i) {
+ return i.method();
+}
+
+TupleInStruct emval_test_call_method2(Interface& i, const TupleInStruct& arg1, float arg2) {
+ return i.method2(arg1, arg2);
+}
+
+void emval_test_call_method3(Interface& i) {
+ i.method3();
+}
+
+void emval_test_call_function(val v, int i, float f, TupleVector tv, StructVector sv) {
+ v(i, f, tv, sv);
+}
+
+EMSCRIPTEN_BINDINGS(([]() {
+ function("mallinfo", &emval_test_mallinfo);
+
+ function("emval_test_new_integer", &emval_test_new_integer);
+ function("emval_test_new_string", &emval_test_new_string);
+ function("emval_test_new_object", &emval_test_new_object);
+ function("emval_test_passthrough_unsigned", &emval_test_passthrough_unsigned);
+ function("emval_test_passthrough", &emval_test_passthrough);
+ function("emval_test_return_void", &emval_test_return_void);
+ function("emval_test_not", &emval_test_not);
+
+ function("emval_test_as_unsigned", &emval_test_as_unsigned);
+ function("emval_test_get_length", &emval_test_get_length);
+ function("emval_test_add", &emval_test_add);
+ function("emval_test_sum", &emval_test_sum);
+
+ //function("emval_test_take_and_return_const_char_star", &emval_test_take_and_return_const_char_star);
+ function("emval_test_take_and_return_std_string", &emval_test_take_and_return_std_string);
+ function("emval_test_take_and_return_std_string_const_ref", &emval_test_take_and_return_std_string_const_ref);
+
+ //function("emval_test_take_and_return_CustomStruct", &emval_test_take_and_return_CustomStruct);
+
+ value_tuple<TupleVector>("TupleVector")
+ .element(&TupleVector::x)
+ .element(&TupleVector::y)
+ //.element(&TupleVector::z)
+ .element(&readTupleVectorZ, &writeTupleVectorZ)
+ ;
+
+ function("emval_test_return_TupleVector", &emval_test_return_TupleVector);
+ function("emval_test_take_and_return_TupleVector", &emval_test_take_and_return_TupleVector);
+
+ value_tuple<TupleVectorTuple>("TupleVectorTuple")
+ .element(&TupleVectorTuple::v)
+ ;
+
+ function("emval_test_return_TupleVectorTuple", &emval_test_return_TupleVectorTuple);
+
+ value_struct<StructVector>("StructVector")
+ .field("x", &StructVector::x)
+ .field("y", &StructVector::y)
+ .field("z", &StructVector::z)
+ ;
+
+ function("emval_test_return_StructVector", &emval_test_return_StructVector);
+ function("emval_test_take_and_return_StructVector", &emval_test_take_and_return_StructVector);
+
+ value_struct<TupleInStruct>("TupleInStruct")
+ .field("field", &TupleInStruct::field)
+ ;
+
+ function("emval_test_take_and_return_TupleInStruct", &emval_test_take_and_return_TupleInStruct);
+
+ class_<ValHolder>("ValHolder")
+ .constructor<val>()
+ .method("getVal", &ValHolder::getVal)
+ .method("setVal", &ValHolder::setVal)
+ .classmethod("some_class_method", &ValHolder::some_class_method)
+ ;
+ function("emval_test_return_ValHolder", &emval_test_return_ValHolder);
+ function("emval_test_set_ValHolder_to_empty_object", &emval_test_set_ValHolder_to_empty_object);
+
+ class_<StringHolder>("StringHolder")
+ .constructor<std::string>()
+ .method("set", &StringHolder::set)
+ .method("get", &StringHolder::get)
+ ;
+
+ class_<CustomStruct>("CustomStruct")
+ .constructor<>()
+ .field("field", &CustomStruct::field)
+ ;
+
+ enum_<Enum>("Enum")
+ .value("ONE", ONE)
+ .value("TWO", TWO)
+ ;
+ function("emval_test_take_and_return_Enum", &emval_test_take_and_return_Enum);
+
+ enum_<EnumClass>("EnumClass")
+ .value("ONE", EnumClass::ONE)
+ .value("TWO", EnumClass::TWO)
+ ;
+ function("emval_test_take_and_return_EnumClass", &emval_test_take_and_return_EnumClass);
+
+ class InterfaceWrapper : public wrapper<Interface> {
+ int method() {
+ return call<int>("method");
+ }
+ TupleInStruct method2(const TupleInStruct& arg1, float arg2) {
+ return call<TupleInStruct>("method2", arg1, arg2);
+ }
+ void method3() {
+ return call<void>("method3");
+ }
+ };
+ interface<InterfaceWrapper>("Interface")
+ ;
+ function("emval_test_call_method", &emval_test_call_method);
+ function("emval_test_call_method2", &emval_test_call_method2);
+ function("emval_test_call_method3", &emval_test_call_method3);
+
+ function("emval_test_call_function", &emval_test_call_function);
+}));
diff --git a/tests/embind/embind_test.js b/tests/embind/embind_test.js new file mode 100644 index 00000000..326bf740 --- /dev/null +++ b/tests/embind/embind_test.js @@ -0,0 +1,341 @@ +module({ + Emscripten: '../build/Emscripten.js' +}, function(imports) { + var cm = imports.Emscripten; + + var checkForLeaks = { + setUp: function() { + this.originalBlockCount = cm.mallinfo().uordblks; + }, + tearDown: function() { + assert.equal(this.originalBlockCount, cm.mallinfo().uordblks); + }, + }; + + fixture("embind", { + baseFixture: checkForLeaks, + + "test value creation": function() { + assert.equal(15, cm.emval_test_new_integer()); + assert.equal("Hello everyone", cm.emval_test_new_string()); + + var object = cm.emval_test_new_object(); + assert.equal('bar', object.foo); + assert.equal(1, object.baz); + }, + + "test passthrough": function() { + var a = {foo: 'bar'}; + var b = cm.emval_test_passthrough(a); + a.bar = 'baz'; + assert.equal('baz', b.bar); + + assert.equal(0, cm.count_emval_handles()); + }, + + "test void return converts to undefined": function() { + assert.equal(undefined, cm.emval_test_return_void()); + }, + + "test booleans can be marshalled": function() { + assert.equal(false, cm.emval_test_not(true)); + assert.equal(true, cm.emval_test_not(false)); + }, + + "test convert double to unsigned": function() { + var rv = cm.emval_test_as_unsigned(1.5); + assert.equal('number', typeof rv); + assert.equal(1, rv); + assert.equal(0, cm.count_emval_handles()); + }, + + "test get length of array": function() { + assert.equal(10, cm.emval_test_get_length([0, 1, 2, 3, 4, 5, 'a', 'b', 'c', 'd'])); + assert.equal(0, cm.count_emval_handles()); + }, + + "test add a bunch of things": function() { + assert.equal(66.0, cm.emval_test_add(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)); + assert.equal(0, cm.count_emval_handles()); + }, + + "test sum array": function() { + assert.equal(66, cm.emval_test_sum([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])); + assert.equal(0, cm.count_emval_handles()); + }, + + "test strings": function() { + assert.equal("foobar", "foo" + "bar"); + assert.equal("foobar", cm.emval_test_take_and_return_std_string("foobar")); + + assert.equal("foobar", cm.emval_test_take_and_return_std_string_const_ref("foobar")); + }, + + "test no memory leak when passing strings in by const reference": function() { + var original = cm.mallinfo().uordblks; + cm.emval_test_take_and_return_std_string_const_ref("foobar"); + assert.equal(original, cm.mallinfo().uordblks); + }, + }); + + fixture("classes", { + baseFixture: checkForLeaks, + + "test class instance": function() { + var a = {foo: 'bar'}; + var c = new cm.ValHolder(a); + assert.equal('bar', c.getVal().foo); + + c.setVal('1234'); + assert.equal('1234', c.getVal()); + + c.delete(); + assert.equal(0, cm.count_emval_handles()); + }, + + "test class methods": function() { + assert.equal(10, cm.ValHolder.some_class_method(10)); + }, + + "test can't call methods on deleted class instances": function() { + var c = new cm.ValHolder(undefined); + c.delete(); + assert.throws(cm.BindingError, function() { + c.getVal(); + }); + assert.throws(cm.BindingError, function() { + c.delete(); + }); + }, + + "test isinstance": function() { + var c = new cm.ValHolder(undefined); + assert.instanceof(c, cm.ValHolder); + c.delete(); + }, + + "test can return class instances by value": function() { + var c = cm.emval_test_return_ValHolder(); + assert.deepEqual({}, c.getVal()); + c.delete(); + }, + + "test can pass class instances to functions by reference": function() { + var a = {a:1}; + var c = new cm.ValHolder(a); + cm.emval_test_set_ValHolder_to_empty_object(c); + assert.deepEqual({}, c.getVal()); + c.delete(); + }, + + "test can access struct fields": function() { + var c = new cm.CustomStruct(); + assert.equal(10, c.field); + c.delete(); + }, + + "test can set struct fields": function() { + var c = new cm.CustomStruct(); + c.field = 15; + assert.equal(15, c.field); + c.delete(); + }, + + "test assignment returns value": function() { + var c = new cm.CustomStruct(); + assert.equal(15, c.field = 15); + c.delete(); + }, + + "test assigning string to integer raises TypeError": function() { + var c = new cm.CustomStruct(); + + var e = assert.throws(TypeError, function() { + c.field = "hi"; + }); + assert.equal('Cannot convert "hi" to int', e.message); + + var e = assert.throws(TypeError, function() { + c.field = {foo:'bar'}; + }); + assert.equal('Cannot convert "[object Object]" to int', e.message); + + c.delete(); + }, + + "test can return tuples by value": function() { + var c = cm.emval_test_return_TupleVector(); + assert.deepEqual([1, 2, 3], c); + }, + + "test tuples can contain tuples": function() { + var c = cm.emval_test_return_TupleVectorTuple(); + assert.deepEqual([[1, 2, 3]], c); + }, + + "test can pass tuples by value": function() { + var c = cm.emval_test_take_and_return_TupleVector([4, 5, 6]); + assert.deepEqual([4, 5, 6], c); + }, + + "test can return structs by value": function() { + var c = cm.emval_test_return_StructVector(); + assert.deepEqual({x: 1, y: 2, z: 3}, c); + }, + + "test can pass structs by value": function() { + var c = cm.emval_test_take_and_return_StructVector({x: 4, y: 5, z: 6}); + assert.deepEqual({x: 4, y: 5, z: 6}, c); + }, + + "test can pass and return tuples in structs": function() { + var d = cm.emval_test_take_and_return_TupleInStruct({field: [1, 2, 3]}); + assert.deepEqual({field: [1, 2, 3]}, d); + }, + + "test can clone handles": function() { + assert.equal(0, cm.count_emval_handles()); + + var a = new cm.ValHolder({}); + var b = a.clone(); + a.delete(); + + assert.equal(1, cm.count_emval_handles()); + + assert.throws(cm.BindingError, function() { + a.delete(); + }); + b.delete(); + + assert.equal(0, cm.count_emval_handles()); + }, + + "test can't clone if already deleted": function() { + var a = new cm.ValHolder({}); + a.delete(); + assert.throws(cm.BindingError, function() { + a.clone(); + }); + }, + + "test moving handles is a clone+delete": function() { + var a = new cm.ValHolder({}); + var b = a.move(); + assert.throws(cm.BindingError, function() { + a.delete(); + }); + assert.equal(1, cm.count_emval_handles()); + b.delete(); + assert.equal(0, cm.count_emval_handles()); + }, + + "test StringHolder": function() { + var a = new cm.StringHolder("foobar"); + assert.equal("foobar", a.get()); + + a.set("barfoo"); + assert.equal("barfoo", a.get()); + a.delete(); + }, + }); + + fixture("embind enumerations", { + baseFixture: checkForLeaks, + + "test can compare enumeration values": function() { + assert.equal(cm.Enum.ONE, cm.Enum.ONE); + assert.notEqual(cm.Enum.ONE, cm.Enum.TWO); + }, + + "test repr includes enum value": function() { + assert.equal('<#Enum_ONE {}>', IMVU.repr(cm.Enum.ONE)); + assert.equal('<#Enum_TWO {}>', IMVU.repr(cm.Enum.TWO)); + }, + + "test instanceof": function() { + assert.instanceof(cm.Enum.ONE, cm.Enum); + }, + + "test can pass and return enumeration values to functions": function() { + assert.equal(cm.Enum.TWO, cm.emval_test_take_and_return_Enum(cm.Enum.TWO)); + }, + }); + + fixture("C++11 enum class", { + baseFixture: checkForLeaks, + + "test can compare enumeration values": function() { + assert.equal(cm.EnumClass.ONE, cm.EnumClass.ONE); + assert.notEqual(cm.EnumClass.ONE, cm.EnumClass.TWO); + }, + + "test repr includes enum value": function() { + assert.equal('<#EnumClass_ONE {}>', IMVU.repr(cm.EnumClass.ONE)); + assert.equal('<#EnumClass_TWO {}>', IMVU.repr(cm.EnumClass.TWO)); + }, + + "test instanceof": function() { + assert.instanceof(cm.EnumClass.ONE, cm.EnumClass); + }, + + "test can pass and return enumeration values to functions": function() { + assert.equal(cm.EnumClass.TWO, cm.emval_test_take_and_return_EnumClass(cm.EnumClass.TWO)); + }, + }); + + fixture("emval call tests", { + "test can call functions from C++": function() { + var called = false; + cm.emval_test_call_function(function(i, f, tv, sv) { + called = true; + assert.equal(10, i); + assert.equal(1.5, f); + assert.deepEqual([1.25, 2.5, 3.75], tv); + assert.deepEqual({x: 1.25, y: 2.5, z: 3.75}, sv); + }, 10, 1.5, [1.25, 2.5, 3.75], {x: 1.25, y: 2.5, z: 3.75}); + assert.true(called); + }, + }); + + fixture("interfaces", { + baseFixture: checkForLeaks, + + "test can wrap JS object in native interface": function() { + var foo = { + calls: [], + method: function() { + this.calls.push('called'); + return 10; + } + }; + + assert.equal(10, cm.emval_test_call_method(foo)); + assert.deepEqual(['called'], foo.calls); + }, + + "test can pass arguments and return complicated values": function() { + var calls = []; + var foo = { + method2: function(arg1, arg2) { + calls.push([arg1, arg2]); + return arg1; + } + }; + + var result = cm.emval_test_call_method2(foo, {field: [1, 2, 3]}, 7); + assert.deepEqual({field: [1, 2, 3]}, result); + assert.deepEqual([[{field: [1, 2, 3]}, 7]], calls); + }, + + "test can call interface methods that return nothing": function() { + var calls = []; + var foo = { + method3: function() { + calls.push('called'); + } + }; + cm.emval_test_call_method3(foo); + assert.deepEqual(['called'], calls); + }, + }); +}); |