Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion site/source/docs/api_reference/bind.h.rst
Original file line number Diff line number Diff line change
Expand Up @@ -804,11 +804,12 @@ Enums
A typedef of ``EnumType`` (a typename for the class).


.. cpp:function:: enum_(const char* name)
.. cpp:function:: enum_(const char* name, enum_value_type valueType = enum_value_type::object)

Constructor.

:param const char* name:
:param enum_value_type valueType: The type of the enum values. This determines how the values are represented in JavaScript. If ``valueType`` is ``enum_value_type::object`` (default), the enum values are represented as an object with a ``.value`` field. If ``valueType`` is ``enum_value_type::number``, the enum values are represented as plain numbers. The default is ``enum_value_type::string``, the enum values are represented as the string names of the enum values.


.. cpp:function:: enum_& value(const char* name, EnumType value)
Expand Down
27 changes: 27 additions & 0 deletions site/source/docs/porting/connecting_cpp_and_javascript/embind.rst
Original file line number Diff line number Diff line change
Expand Up @@ -825,6 +825,33 @@ type.
Module.OldStyle.ONE;
Module.NewStyle.TWO;

You can simplify how enums are represented in JavaScript by setting the `enum_value_type` parameter when registering the enum.
The default value type is `enum_value_type::object`, which binds the enum values to objects with a `value` property.
Other options are `enum_value_type::number`, which binds the enum values to their plain integer value, and `enum_value_type::string`, which binds the enum values to the string of their name.

.. code:: cpp

EMSCRIPTEN_BINDINGS(my_enum_example) {
enum_<FirstEnum>("ObjectEnum", enum_value_type::object)
.value("ONE", FirstEnum::ONE)
.value("TWO", FirstEnum::TWO)
;
enum_<SecondEnum>("NumberEnum", enum_value_type::number)
.value("ONE", SecondEnum::ONE)
.value("TWO", SecondEnum::TWO)
;
enum_<ThirdEnum>("StringEnum", enum_value_type::string)
.value("ONE", ThirdEnum::ONE)
.value("TWO", ThirdEnum::TWO)
;
}

.. code:: javascript

Module.ObjectEnum.ONE.value === 1;
Module.NumberEnum.ONE === 1;
Module.StringEnum.ONE === "ONE";

.. _embind-constants:

Constants
Expand Down
109 changes: 86 additions & 23 deletions src/lib/libembind.js
Original file line number Diff line number Diff line change
Expand Up @@ -2196,39 +2196,102 @@ var LibraryEmbind = {

_embind_register_enum__docs: '/** @suppress {globalThis} */',
_embind_register_enum__deps: ['$exposePublicSymbol', '$enumReadValueFromPointer',
'$AsciiToString', '$registerType'],
_embind_register_enum: (rawType, name, size, isSigned) => {
'$AsciiToString', '$registerType', '$getEnumValueType'],
_embind_register_enum: (rawType, name, size, isSigned, rawValueType) => {
name = AsciiToString(name);
const valueType = getEnumValueType(rawValueType);

switch (valueType) {
case 'object': {
function ctor() {}
ctor.values = {};

registerType(rawType, {
name,
constructor: ctor,
valueType,
fromWireType: function(c) {
return this.constructor.values[c];
},
toWireType: (destructors, c) => c.value,
readValueFromPointer: enumReadValueFromPointer(name, size, isSigned),
destructorFunction: null,
});

function ctor() {}
ctor.values = {};
exposePublicSymbol(name, ctor);
break;
}
case 'number': {
var keysMap = {};

registerType(rawType, {
name: name,
policyType: 'value',
fromWireType: (c) => c,
toWireType: (destructors, c) => c,
readValueFromPointer: enumReadValueFromPointer(name, size, isSigned),
destructorFunction: null,
});

registerType(rawType, {
name,
constructor: ctor,
fromWireType: function(c) {
return this.constructor.values[c];
},
toWireType: (destructors, c) => c.value,
readValueFromPointer: enumReadValueFromPointer(name, size, isSigned),
destructorFunction: null,
});
exposePublicSymbol(name, ctor);
exposePublicSymbol(name, keysMap);
// Just exposes a simple dict. argCount is meaningless here,
delete Module[name].argCount;
break;
}
case 'string': {
var valuesMap = {};
var reverseMap = {};
var keysMap = {};

registerType(rawType, {
name: name,
valuesMap,
reverseMap,
keysMap,
valueType,
fromWireType: function(c) {
return this.reverseMap[c];
},
toWireType: function(destructors, c) {
return this.valuesMap[c];
},
readValueFromPointer: enumReadValueFromPointer(name, size, isSigned),
destructorFunction: null,
});

exposePublicSymbol(name, keysMap);
// Just exposes a simple dict. argCount is meaningless here,
delete Module[name].argCount;
}
}
},

_embind_register_enum_value__deps: ['$createNamedFunction', '$AsciiToString', '$requireRegisteredType'],
_embind_register_enum_value: (rawEnumType, name, enumValue) => {
var enumType = requireRegisteredType(rawEnumType, 'enum');
name = AsciiToString(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;
switch (enumType.valueType) {
case 'object': {
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;
break;
}
case 'number': {
enumType.keysMap[name] = enumValue;
break;
}
case 'string': {
enumType.valuesMap[name] = enumValue;
enumType.reverseMap[enumValue] = name;
enumType.keysMap[name] = name;
}
}
},

_embind_register_constant__deps: ['$AsciiToString', '$whenDependentTypesAreResolved'],
Expand Down
38 changes: 30 additions & 8 deletions src/lib/libembind_gen.js
Original file line number Diff line number Diff line change
Expand Up @@ -279,23 +279,35 @@ var LibraryEmbind = {
},
$EnumDefinition: class {
hasPublicSymbol = true;
constructor(typeId, name) {
constructor(typeId, name, valueType) {
this.typeId = typeId;
this.name = name;
this.items = [];
this.destructorType = 'none';
this.valueType = valueType;
}

print(nameMap, out) {
out.push(`export interface ${this.name}Value<T extends number> {\n`);
out.push(' value: T;\n}\n');
if (this.valueType === 'object') {
out.push(`export interface ${this.name}Value<T extends number> {\n`);
out.push(' value: T;\n}\n');
}
out.push(`export type ${this.name} = `);
if (this.items.length === 0) {
out.push('never/* Empty Enumerator */');
} else {
const outItems = [];
for (const [name, value] of this.items) {
outItems.push(`${this.name}Value<${value}>`);
switch (this.valueType) {
case 'object':
outItems.push(`${this.name}Value<${value}>`);
break;
case 'string':
outItems.push(`'${name}'`);
break;
case 'number':
outItems.push(`${value}`);
}
}
out.push(outItems.join('|'));
}
Expand All @@ -306,7 +318,16 @@ var LibraryEmbind = {
out.push(` ${this.name}: {`);
const outItems = [];
for (const [name, value] of this.items) {
outItems.push(`${name}: ${this.name}Value<${value}>`);
switch (this.valueType) {
case 'object':
outItems.push(`${name}: ${this.name}Value<${value}>`);
break;
case 'string':
outItems.push(`${name}: '${name}'`);
break;
case 'number':
outItems.push(`${name}: ${value}`);
}
}
out.push(outItems.join(', '));
out.push('};\n');
Expand Down Expand Up @@ -711,10 +732,11 @@ var LibraryEmbind = {
},
// Stub function. This is called a when extending an object and not needed for TS generation.
_embind_create_inheriting_constructor: (constructorName, wrapperType, properties) => {},
_embind_register_enum__deps: ['$AsciiToString', '$EnumDefinition', '$moduleDefinitions'],
_embind_register_enum: function(rawType, name, size, isSigned) {
_embind_register_enum__deps: ['$AsciiToString', '$EnumDefinition', '$moduleDefinitions', '$getEnumValueType'],
_embind_register_enum: function(rawType, name, size, isSigned, rawValueType) {
name = AsciiToString(name);
const enumDef = new EnumDefinition(rawType, name);
const valueType = getEnumValueType(rawValueType);
const enumDef = new EnumDefinition(rawType, name, valueType);
registerType(rawType, enumDef);
moduleDefinitions.push(enumDef);
},
Expand Down
5 changes: 5 additions & 0 deletions src/lib/libembind_shared.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,11 @@ var LibraryEmbindShared = {
}
},

$getEnumValueType(rawValueType) {
// This must match the values of enum_value_type in wire.h
return rawValueType === 0 ? 'object' : (rawValueType === 1 ? 'number' : 'string');
},

$getRequiredArgCount(argTypes) {
var requiredArgCount = argTypes.length - 2;
for (var i = argTypes.length - 1; i >= 2; --i) {
Expand Down
2 changes: 1 addition & 1 deletion src/lib/libsigs.js
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ sigs = {
_embind_register_class_property__sig: 'vpppppppppp',
_embind_register_constant__sig: 'vppd',
_embind_register_emval__sig: 'vp',
_embind_register_enum__sig: 'vpppi',
_embind_register_enum__sig: 'vpppii',
_embind_register_enum_value__sig: 'vppi',
_embind_register_float__sig: 'vppp',
_embind_register_function__sig: 'vpippppii',
Expand Down
8 changes: 5 additions & 3 deletions system/include/emscripten/bind.h
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,8 @@ void _embind_register_enum(
TYPEID enumType,
const char* name,
size_t size,
bool isSigned);
bool isSigned,
int policyValue);

void _embind_register_smart_ptr(
TYPEID pointerType,
Expand Down Expand Up @@ -1989,13 +1990,14 @@ class enum_ {
public:
typedef EnumType enum_type;

enum_(const char* name) {
enum_(const char* name, enum_value_type valueType = enum_value_type::object) {
using namespace internal;
_embind_register_enum(
internal::TypeID<EnumType>::get(),
name,
sizeof(EnumType),
std::is_signed<typename std::underlying_type<EnumType>::type>::value);
std::is_signed<typename std::underlying_type<EnumType>::type>::value,
static_cast<int>(valueType));
}

enum_& value(const char* name, EnumType value) {
Expand Down
6 changes: 6 additions & 0 deletions system/include/emscripten/wire.h
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,12 @@ struct reference : public allow_raw_pointers {};

} // end namespace return_value_policy

enum class enum_value_type {
object = 0,
number = 1,
string = 2
};

namespace internal {

#if __cplusplus >= 201703L
Expand Down
41 changes: 41 additions & 0 deletions test/embind/embind.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2031,6 +2031,47 @@ module({
});
});

BaseFixture.extend("enums with integer values", function() {
test("can compare enumeration values", function() {
assert.equal(cm.EnumNum.ONE, cm.EnumNum.ONE);
assert.equal(cm.EnumNum.ONE, 0);
assert.notEqual(cm.EnumNum.TWO, cm.EnumNum.ONE);
});

if (typeof INVOKED_FROM_EMSCRIPTEN_TEST_RUNNER === "undefined") { // TODO: Enable this to work in Emscripten runner as well!
test("repr includes enum value", function() {
assert.equal(0, IMVU.repr(cm.EnumNum.ONE));
assert.equal(1, IMVU.repr(cm.EnumNum.TWO));
});
}

test("can pass and return enumeration values to functions", function() {
assert.equal(cm.EnumNum.TWO, cm.emval_test_take_and_return_EnumNum(cm.EnumNum.TWO));
assert.equal(cm.EnumNum.TWO, cm.emval_test_take_and_return_EnumNum(cm.EnumNum.TWO));
});
});


BaseFixture.extend("enums with string values", function() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a test for integers enums as well?

test("can compare enumeration values", function() {
assert.equal(cm.EnumStr.ONE, cm.EnumStr.ONE);
assert.equal(cm.EnumStr.ONE, 'ONE');
assert.notEqual(cm.EnumStr.ONE, cm.EnumStr.TWO);
});

if (typeof INVOKED_FROM_EMSCRIPTEN_TEST_RUNNER === "undefined") { // TODO: Enable this to work in Emscripten runner as well!
test("repr includes enum value", function() {
assert.equal('ONE', IMVU.repr(cm.EnumStr.ONE));
assert.equal('TWO', IMVU.repr(cm.EnumStr.TWO));
});
}

test("can pass and return enumeration values to functions", function() {
assert.equal(cm.EnumStr.TWO, cm.emval_test_take_and_return_EnumStr(cm.EnumStr.TWO));
assert.equal('TWO', cm.emval_test_take_and_return_EnumStr('TWO'));
});
});

BaseFixture.extend("emval call tests", function() {
test("can call functions from C++", function() {
var called = false;
Expand Down
Loading