From 86305a27d4552167f76dd9e19e0c8e45aa645caa Mon Sep 17 00:00:00 2001 From: Chad Austin Date: Tue, 25 Sep 2012 17:43:20 -0700 Subject: [PATCH 1/6] Add embind headers --- system/include/emscripten/bind.h | 602 +++++++++++++++++++++++++++++++ system/include/emscripten/val.h | 177 +++++++++ system/include/emscripten/wire.h | 223 ++++++++++++ 3 files changed, 1002 insertions(+) create mode 100644 system/include/emscripten/bind.h create mode 100644 system/include/emscripten/val.h create mode 100644 system/include/emscripten/wire.h diff --git a/system/include/emscripten/bind.h b/system/include/emscripten/bind.h new file mode 100644 index 000000000..0f1997bb7 --- /dev/null +++ b/system/include/emscripten/bind.h @@ -0,0 +1,602 @@ +#pragma once + +#include +#include +#include +#include +#include + +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 + BindingsDefinition(Function fn) { + fn(); + } + }; + } +} + +namespace emscripten { + namespace internal { + template + struct Invoker { + static typename internal::BindingType::WireType invoke( + ReturnType (fn)(Args...), + typename internal::BindingType::WireType... args + ) { + return internal::BindingType::toWireType( + fn( + internal::BindingType::fromWireType(args)... + ) + ); + } + }; + + template + struct Invoker { + static void invoke( + void (fn)(Args...), + typename internal::BindingType::WireType... args + ) { + return fn( + internal::BindingType::fromWireType(args)... + ); + } + }; + } + + template + void function(const char* name, ReturnType (fn)(Args...)) { + internal::registerStandardTypes(); + + internal::ArgTypeList args; + internal::_embind_register_function( + name, + internal::getTypeID(), + args.count, + args.types, + reinterpret_cast(&internal::Invoker::invoke), + reinterpret_cast(fn)); + } + + namespace internal { + template + ClassType* raw_constructor( + typename internal::BindingType::WireType... args + ) { + return new ClassType( + internal::BindingType::fromWireType(args)... + ); + } + + template + void raw_destructor(ClassType* ptr) { + delete ptr; + } + + template + struct MethodInvoker { + typedef ReturnType (ClassType::*MemberPointer)(Args...); + typename internal::BindingType::WireType invoke( + ClassType* ptr, + const MemberPointer& method, + typename internal::BindingType::WireType... args + ) { + return internal::BindingType::toWireType( + (ptr->*method)( + internal::BindingType::fromWireType(args)... + ) + ); + } + }; + + template + struct MethodInvoker { + typedef void (ClassType::*MemberPointer)(Args...); + static void invoke( + ClassType* ptr, + const MemberPointer& method, + typename internal::BindingType::WireType... args + ) { + return (ptr->*method)( + internal::BindingType::fromWireType(args)... + ); + } + }; + + template + struct ConstMethodInvoker { + typedef ReturnType (ClassType::*MemberPointer)(Args...) const; + static typename internal::BindingType::WireType invoke( + const ClassType* ptr, + const MemberPointer& method, + typename internal::BindingType::WireType... args + ) { + return internal::BindingType::toWireType( + (ptr->*method)( + internal::BindingType::fromWireType(args)... + ) + ); + } + }; + + template + struct ConstMethodInvoker { + typedef void (ClassType::*MemberPointer)(Args...) const; + static void invoke( + const ClassType* ptr, + const MemberPointer& method, + typename internal::BindingType::WireType... args + ) { + return (ptr->*method)( + internal::BindingType::fromWireType(args)... + ); + } + }; + + template + struct FieldAccess { + typedef FieldType ClassType::*MemberPointer; + typedef internal::BindingType 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 + static WireType propertyGet( + ClassType& ptr, + const Getter& getter + ) { + return FieldBinding::toWireType(getter(ptr)); + } + + template + static void propertySet( + ClassType& ptr, + const Setter& setter, + WireType value + ) { + setter(ptr, FieldBinding::fromWireType(value)); + } + }; + } + + template + class value_tuple { + public: + value_tuple(const char* name) { + internal::registerStandardTypes(); + internal::_embind_register_tuple( + internal::getTypeID(), + name, + reinterpret_cast(&internal::raw_constructor), + reinterpret_cast(&internal::raw_destructor)); + } + + template + value_tuple& element(ElementType ClassType::*field) { + internal::_embind_register_tuple_element( + internal::getTypeID(), + internal::getTypeID(), + reinterpret_cast(&internal::FieldAccess::get), + reinterpret_cast(&internal::FieldAccess::set), + sizeof(field), + &field); + + return *this; + } + + template + value_tuple& element(ElementType (*getter)(const ClassType&), void (*setter)(ClassType&, ElementType)) { + internal::_embind_register_tuple_element_accessor( + internal::getTypeID(), + internal::getTypeID(), + reinterpret_cast(&internal::FieldAccess::template propertyGet), + sizeof(getter), + &getter, + reinterpret_cast(&internal::FieldAccess::template propertySet), + sizeof(setter), + &setter); + return *this; + } + + template + value_tuple& element(ElementType (*getter)(const ClassType&), void (*setter)(ClassType&, const ElementType&)) { + internal::_embind_register_tuple_element_accessor( + internal::getTypeID(), + internal::getTypeID(), + reinterpret_cast(&internal::FieldAccess::template propertyGet), + sizeof(getter), + &getter, + reinterpret_cast(&internal::FieldAccess::template propertySet), + sizeof(setter), + &setter); + return *this; + } + + template + value_tuple& element(ElementType (*getter)(const ClassType&), void (*setter)(ClassType&, const ElementType&&)) { + internal::_embind_register_tuple_element_accessor( + internal::getTypeID(), + internal::getTypeID(), + reinterpret_cast(&internal::FieldAccess::template propertyGet), + sizeof(getter), + &getter, + reinterpret_cast(&internal::FieldAccess::template propertySet), + sizeof(setter), + &setter); + return *this; + } + + template + value_tuple& element(ElementType (*getter)(const ClassType&), void (*setter)(ClassType&, ElementType&)) { + internal::_embind_register_tuple_element_accessor( + internal::getTypeID(), + internal::getTypeID(), + reinterpret_cast(&internal::FieldAccess::template propertyGet), + sizeof(getter), + &getter, + reinterpret_cast(&internal::FieldAccess::template propertySet), + sizeof(setter), + &setter); + return *this; + } + }; + + template + class value_struct { + public: + value_struct(const char* name) { + internal::registerStandardTypes(); + internal::_embind_register_struct( + internal::getTypeID(), + name, + reinterpret_cast(&internal::raw_constructor), + reinterpret_cast(&internal::raw_destructor)); + } + + template + value_struct& field(const char* fieldName, FieldType ClassType::*field) { + internal::_embind_register_struct_field( + internal::getTypeID(), + fieldName, + internal::getTypeID(), + reinterpret_cast(&internal::FieldAccess::get), + reinterpret_cast(&internal::FieldAccess::set), + sizeof(field), + &field); + + return *this; + } + }; + + // TODO: support class definitions without constructors. + // TODO: support external class constructors + template + class class_ { + public: + class_(const char* name) { + internal::registerStandardTypes(); + internal::_embind_register_class( + internal::getTypeID(), + name, + reinterpret_cast(&internal::raw_destructor)); + } + + template + class_& constructor() { + internal::ArgTypeList args; + internal::_embind_register_class_constructor( + internal::getTypeID(), + args.count, + args.types, + reinterpret_cast(&internal::raw_constructor)); + } + + template + class_& method(const char* methodName, ReturnType (ClassType::*memberFunction)(Args...)) { + internal::ArgTypeList args; + internal::_embind_register_class_method( + internal::getTypeID(), + methodName, + internal::getTypeID(), + args.count, + args.types, + reinterpret_cast(&internal::MethodInvoker::invoke), + sizeof(memberFunction), + &memberFunction); + return *this; + } + + template + class_& method(const char* methodName, ReturnType (ClassType::*memberFunction)(Args...) const) { + internal::ArgTypeList args; + internal::_embind_register_class_method( + internal::getTypeID(), + methodName, + internal::getTypeID(), + args.count, + args.types, + reinterpret_cast(&internal::ConstMethodInvoker::invoke), + sizeof(memberFunction), + &memberFunction); + return *this; + } + + template + class_& field(const char* fieldName, FieldType ClassType::*field) { + internal::_embind_register_class_field( + internal::getTypeID(), + fieldName, + internal::getTypeID(), + reinterpret_cast(&internal::FieldAccess::get), + reinterpret_cast(&internal::FieldAccess::set), + sizeof(field), + &field); + return *this; + } + + template + class_& classmethod(const char* methodName, ReturnType (*classMethod)(Args...)) { + internal::ArgTypeList args; + internal::_embind_register_class_classmethod( + internal::getTypeID(), + methodName, + internal::getTypeID(), + args.count, + args.types, + reinterpret_cast(classMethod)); + return *this; + } + }; + + template + class enum_ { + public: + enum_(const char* name) { + _embind_register_enum( + internal::getTypeID(), + 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(), + name, + static_cast(value)); + return *this; + } + }; + + template + 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 + ReturnType call(const char* name, Args... args) { + assertInitialized(); + return Caller::call(*jsobj, name, args...); + } + + private: + // this class only exists because you can't partially specialize function templates + template + struct Caller { + static ReturnType call(val& v, const char* name, Args... args) { + return v.call(name, args...).template as(); + } + }; + + template + struct Caller { + 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()); + } + } + + boost::optional jsobj; + }; + + namespace internal { + template + WrapperType* create_interface_wrapper(EM_VAL e) { + WrapperType* p = new WrapperType; + p->initialize(e); + return p; + } + } + + template + class interface { + public: + typedef typename WrapperType::interface InterfaceType; + + interface(const char* name) { + _embind_register_interface( + internal::getTypeID(), + name, + reinterpret_cast(&internal::create_interface_wrapper), + reinterpret_cast(&internal::raw_destructor)); + } + }; +} + +#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 000000000..96db93264 --- /dev/null +++ b/system/include/emscripten/val.h @@ -0,0 +1,177 @@ +#pragma once + +#include // uintptr_t +#include + +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 + val operator()(Args... args) { + internal::ArgTypeList argList; + typedef internal::EM_VAL (*TypedCall)( + internal::EM_VAL, + unsigned, + internal::TypeID argTypes[], + typename internal::BindingType::WireType...); + TypedCall typedCall = reinterpret_cast(&internal::_emval_call); + return val( + typedCall( + handle, + argList.count, + argList.types, + internal::toWireType(args)...)); + } + + template + val call(const char* name, Args... args) { + internal::ArgTypeList argList; + typedef internal::EM_VAL (*TypedCall)( + internal::EM_VAL, + const char* name, + unsigned, + internal::TypeID argTypes[], + typename internal::BindingType::WireType...); + TypedCall typedCall = reinterpret_cast(&internal::_emval_call_method); + return val( + typedCall( + handle, + name, + argList.count, + argList.types, + internal::toWireType(args)...)); + } + + template + T as() const { + typedef internal::BindingType BT; + + typedef typename BT::WireType (*TypedAs)( + internal::EM_VAL value, + emscripten::internal::TypeID returnType); + TypedAs typedAs = reinterpret_cast(&internal::_emval_as); + + typename BT::WireType wt = typedAs(handle, internal::getTypeID()); + internal::WireDeleter 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; + }; + + namespace internal { + template<> + struct BindingType { + 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 000000000..722bf4b8f --- /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 + inline TypeID getTypeID() { + return reinterpret_cast(&typeid(T)); + } + + // count<> + + template + struct count; + + template<> + struct count<> { + enum { value = 0 }; + }; + + template + struct count { + enum { value = 1 + count::value }; + }; + + // ArgTypeList<> + + template + struct ArgTypes; + + template<> + struct ArgTypes<> { + static void fill(TypeID* argTypes) { + } + }; + + template + struct ArgTypes { + static void fill(TypeID* argTypes) { + *argTypes = getTypeID(); + return ArgTypes::fill(argTypes + 1); + } + }; + + template + struct ArgTypeList { + enum { args_count = count::value }; + + ArgTypeList() { + count = args_count; + ArgTypes::fill(types); + } + + unsigned count; + TypeID types[args_count]; + }; + + // BindingType + + template + struct BindingType; + +#define EMSCRIPTEN_DEFINE_NATIVE_BINDING_TYPE(type) \ + template<> \ + struct BindingType { \ + 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 { + }; + + template<> + struct BindingType { + typedef bool WireType; + static WireType toWireType(bool b) { + return b; + } + static bool fromWireType(WireType wt) { + return wt; + } + static void destroy(WireType) { + } + }; + + template<> + struct BindingType { + 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 { + 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 EnumBindingType { + typedef Enum WireType; + + static WireType toWireType(Enum v) { + return v; + } + static Enum fromWireType(WireType v) { + return v; + } + }; + + template + struct GenericBindingType { + typedef typename std::remove_reference::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 + struct WireDeleter { + typedef typename BindingType::WireType WireType; + + WireDeleter(WireType wt) + : wt(wt) + {} + + ~WireDeleter() { + BindingType::destroy(wt); + } + + WireType wt; + }; + + // catch-all generic binding + template + struct BindingType : std::conditional< + std::is_enum::value, + EnumBindingType, + GenericBindingType>::type + {}; + + template + auto toWireType(const T& v) -> typename BindingType::WireType { + return BindingType::toWireType(v); + } + + } +} From 2530052556075021ed59f0f7e3b1028ee1030556 Mon Sep 17 00:00:00 2001 From: Chad Austin Date: Tue, 25 Sep 2012 17:54:54 -0700 Subject: [PATCH 2/6] Move embind C++ implementation into emscripten repository --- system/lib/embind/bind.cpp | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100755 system/lib/embind/bind.cpp diff --git a/system/lib/embind/bind.cpp b/system/lib/embind/bind.cpp new file mode 100755 index 000000000..b63a86aae --- /dev/null +++ b/system/lib/embind/bind.cpp @@ -0,0 +1,34 @@ +#include + +using namespace emscripten; + +namespace emscripten { + namespace internal { + void registerStandardTypes() { + static bool first = true; + if (first) { + first = false; + + _embind_register_void(getTypeID(), "void"); + + _embind_register_bool(getTypeID(), "bool", true, false); + + _embind_register_integer(getTypeID(), "char"); + _embind_register_integer(getTypeID(), "signed char"); + _embind_register_integer(getTypeID(), "unsigned char"); + _embind_register_integer(getTypeID(), "short"); + _embind_register_integer(getTypeID(), "unsigned short"); + _embind_register_integer(getTypeID(), "int"); + _embind_register_integer(getTypeID(), "unsigned int"); + _embind_register_integer(getTypeID(), "long"); + _embind_register_integer(getTypeID(), "unsigned long"); + + _embind_register_float(getTypeID(), "float"); + _embind_register_float(getTypeID(), "double"); + + _embind_register_cstring(getTypeID(), "std::string"); + _embind_register_emval(getTypeID(), "emscripten::val"); + } + } + } +} From b1f14cf7cd65b082d57648d60dc36138b304707d Mon Sep 17 00:00:00 2001 From: Chad Austin Date: Tue, 25 Sep 2012 18:03:21 -0700 Subject: [PATCH 3/6] Move embind/emval into emscripten proper --- src/embind/embind.js | 609 +++++++++++++++++++++++++++++++++++++++++++ src/embind/emval.js | 111 ++++++++ 2 files changed, 720 insertions(+) create mode 100644 src/embind/embind.js create mode 100644 src/embind/emval.js diff --git a/src/embind/embind.js b/src/embind/embind.js new file mode 100644 index 000000000..824058eee --- /dev/null +++ b/src/embind/embind.js @@ -0,0 +1,609 @@ +/*global Module*/ +/*global _malloc, _free, _memcpy*/ +/*global FUNCTION_TABLE, HEAP32*/ +/*global Pointer_stringify, writeStringToMemory*/ +/*global __emval_register, _emval_handle_array, __emval_decref*/ + +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 = IMVU.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 = IMVU.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: IMVU.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 000000000..9574ab379 --- /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); +} From ec45caf434e7e3050a73b42ab9a9a05fba3d723f Mon Sep 17 00:00:00 2001 From: Chad Austin Date: Tue, 25 Sep 2012 18:28:03 -0700 Subject: [PATCH 4/6] add embind tests --- tests/embind/embind_test.cpp | 335 ++++++++++++++++++++++++++++++++++ tests/embind/embind_test.js | 341 +++++++++++++++++++++++++++++++++++ 2 files changed, 676 insertions(+) create mode 100644 tests/embind/embind_test.cpp create mode 100644 tests/embind/embind_test.js diff --git a/tests/embind/embind_test.cpp b/tests/embind/embind_test.cpp new file mode 100644 index 000000000..e7b4d9852 --- /dev/null +++ b/tests/embind/embind_test.cpp @@ -0,0 +1,335 @@ +#include +#include +#include + +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 emval_test_get_length(val v) { + return v.get("length").as(); +} + +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(); + double rv = 0; + for (unsigned i = 0; i < length; ++i) { + rv += v.get(i).as(); + } + 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") + .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") + .element(&TupleVectorTuple::v) + ; + + function("emval_test_return_TupleVectorTuple", &emval_test_return_TupleVectorTuple); + + value_struct("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") + .field("field", &TupleInStruct::field) + ; + + function("emval_test_take_and_return_TupleInStruct", &emval_test_take_and_return_TupleInStruct); + + class_("ValHolder") + .constructor() + .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") + .constructor() + .method("set", &StringHolder::set) + .method("get", &StringHolder::get) + ; + + class_("CustomStruct") + .constructor<>() + .field("field", &CustomStruct::field) + ; + + enum_("Enum") + .value("ONE", ONE) + .value("TWO", TWO) + ; + function("emval_test_take_and_return_Enum", &emval_test_take_and_return_Enum); + + enum_("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 { + int method() { + return call("method"); + } + TupleInStruct method2(const TupleInStruct& arg1, float arg2) { + return call("method2", arg1, arg2); + } + void method3() { + return call("method3"); + } + }; + interface("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 000000000..326bf7400 --- /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); + }, + }); +}); From 5208548e7f1db587491e91309ea883917545ba67 Mon Sep 17 00:00:00 2001 From: Chad Austin Date: Tue, 25 Sep 2012 23:25:30 -0700 Subject: [PATCH 5/6] Break embind's dependency on boost::optional --- system/include/emscripten/bind.h | 47 ++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/system/include/emscripten/bind.h b/system/include/emscripten/bind.h index 0f1997bb7..8f56ff87a 100644 --- a/system/include/emscripten/bind.h +++ b/system/include/emscripten/bind.h @@ -2,9 +2,9 @@ #include #include +#include #include #include -#include namespace emscripten { namespace internal { @@ -525,6 +525,49 @@ namespace emscripten { } }; + namespace internal { + template + 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(&data); + } + + bool initialized; + typename std::aligned_storage::type data; + }; + } + template class wrapper : public InterfaceType { public: @@ -572,7 +615,7 @@ namespace emscripten { } } - boost::optional jsobj; + internal::optional jsobj; }; namespace internal { From 58056b5383b1cdcd4f537fad82f9d4a03fb2556e Mon Sep 17 00:00:00 2001 From: Chad Austin Date: Tue, 25 Sep 2012 23:35:44 -0700 Subject: [PATCH 6/6] Break IMVU dependencies in embind javascript --- src/embind/embind.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/embind/embind.js b/src/embind/embind.js index 824058eee..fff19d869 100644 --- a/src/embind/embind.js +++ b/src/embind/embind.js @@ -4,6 +4,16 @@ /*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') { @@ -352,7 +362,7 @@ function __embind_register_class( name = Pointer_stringify(name); destructor = FUNCTION_TABLE[destructor]; - var Handle = IMVU.createNamedFunction(name, function(ptr) { + var Handle = createNamedFunction(name, function(ptr) { this.count = {value: 1}; this.ptr = ptr; }); @@ -388,7 +398,7 @@ function __embind_register_class( this.ptr = undefined; }; - var constructor = IMVU.createNamedFunction(name, function() { + var constructor = createNamedFunction(name, function() { var body = constructor.body; body.apply(this, arguments); }); @@ -580,7 +590,7 @@ function __embind_register_enum_value( var Value = Object.create(enumType.constructor.prototype, { value: {value: enumValue}, - constructor: {value: IMVU.createNamedFunction(enumType.name + '_' + name, function() {})}, + constructor: {value: createNamedFunction(enumType.name + '_' + name, function() {})}, }); Enum.values[enumValue] = Value; Enum[name] = Value;