Skip to content

Commit ab20b5f

Browse files
committed
feat(embind): add a way to register enums as their plain value or as
their props name
1 parent f6f516c commit ab20b5f

File tree

10 files changed

+179
-109
lines changed

10 files changed

+179
-109
lines changed

site/source/docs/api_reference/bind.h.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -804,12 +804,12 @@ Enums
804804
A typedef of ``EnumType`` (a typename for the class).
805805

806806

807-
.. cpp:function:: enum_(const char* name, bool asString = false)
807+
.. cpp:function:: enum_(const char* name, enum_value_type valueType = enum_value_type::object)
808808

809809
Constructor.
810810

811811
:param const char* name:
812-
:param bool asString: *Experimental.* If true, the enum values are represented by plain strings in JavaScript, which is handy for basic operations like comparison and serialization.
812+
: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.
813813

814814

815815
.. cpp:function:: enum_& value(const char* name, EnumType value)

site/source/docs/porting/connecting_cpp_and_javascript/embind.rst

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -825,20 +825,32 @@ type.
825825
Module.OldStyle.ONE;
826826
Module.NewStyle.TWO;
827827
828-
If you set the `asString` parameter to `true` when registering the enum, the enum values will be represented as plain strings in JavaScript.
828+
You can simplify how enums are represented in JavaScript by setting the `enum_value_type` parameter when registering the enum.
829+
The default value type is `enum_value_type::object`, which binds the enum values to objects with a `value` property.
830+
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.
829831

830832
.. code:: cpp
831833
832834
EMSCRIPTEN_BINDINGS(my_enum_example) {
833-
enum_<MyEnum>("MyEnum", true)
834-
.value("ONE", MyEnum::ONE)
835-
.value("TWO", MyEnum::TWO)
835+
enum_<FirstEnum>("ObjectEnum", enum_value_type::object)
836+
.value("ONE", FirstEnum::ONE)
837+
.value("TWO", FirstEnum::TWO)
838+
;
839+
enum_<SecondEnum>("NumberEnum", enum_value_type::number)
840+
.value("ONE", SecondEnum::ONE)
841+
.value("TWO", SecondEnum::TWO)
842+
;
843+
enum_<ThirdEnum>("StringEnum", enum_value_type::string)
844+
.value("ONE", ThirdEnum::ONE)
845+
.value("TWO", ThirdEnum::TWO)
836846
;
837847
}
838848
839849
.. code:: javascript
840850
841-
Module.MyEnum.ONE === "ONE"; // true
851+
Module.ObjectEnum.ONE.value === 1;
852+
Module.NumberEnum.ONE === 1;
853+
Module.StringEnum.ONE === "ONE";
842854
843855
.. _embind-constants:
844856

src/lib/libembind.js

Lines changed: 85 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -2196,48 +2196,73 @@ var LibraryEmbind = {
21962196

21972197
_embind_register_enum__docs: '/** @suppress {globalThis} */',
21982198
_embind_register_enum__deps: ['$exposePublicSymbol', '$enumReadValueFromPointer',
2199-
'$AsciiToString', '$registerType'],
2200-
_embind_register_enum: (rawType, name, size, isSigned, asString) => {
2199+
'$AsciiToString', '$registerType', '$getEnumValueType'],
2200+
_embind_register_enum: (rawType, name, size, isSigned, rawValueType) => {
22012201
name = AsciiToString(name);
2202+
const valueType = getEnumValueType(rawValueType);
2203+
2204+
switch (valueType) {
2205+
case 'object': {
2206+
function ctor() {}
2207+
ctor.values = {};
2208+
2209+
registerType(rawType, {
2210+
name,
2211+
constructor: ctor,
2212+
valueType,
2213+
fromWireType: function(c) {
2214+
return this.constructor.values[c];
2215+
},
2216+
toWireType: (destructors, c) => c.value,
2217+
readValueFromPointer: enumReadValueFromPointer(name, size, isSigned),
2218+
destructorFunction: null,
2219+
});
22022220

2203-
if (asString) {
2204-
var valuesMap = {};
2205-
var reverseMap = {};
2206-
var keysMap = {};
2207-
2208-
registerType(rawType, {
2209-
name: name,
2210-
valuesMap,
2211-
reverseMap,
2212-
keysMap,
2213-
asString,
2214-
fromWireType: function(c) {
2215-
return this.reverseMap[c];
2216-
},
2217-
toWireType: function(destructors, c) {
2218-
return this.valuesMap[c];
2219-
},
2220-
readValueFromPointer: enumReadValueFromPointer(name, size, isSigned),
2221-
destructorFunction: null,
2222-
});
2223-
exposePublicSymbol(name, keysMap);
2224-
// Just exposes a simple dict. argCount is meaningless here,
2225-
delete Module[name].argCount;
2226-
} else {
2227-
function ctor() {}
2228-
ctor.values = {};
2229-
2230-
registerType(rawType, {
2231-
name,
2232-
constructor: ctor,
2233-
fromWireType: function(c) {
2234-
return this.constructor.values[c];
2235-
},
2236-
toWireType: (destructors, c) => c.value,
2237-
readValueFromPointer: enumReadValueFromPointer(name, size, isSigned),
2238-
destructorFunction: null,
2239-
});
2240-
exposePublicSymbol(name, ctor);
2221+
exposePublicSymbol(name, ctor);
2222+
break;
2223+
}
2224+
case 'number': {
2225+
var keysMap = {};
2226+
2227+
registerType(rawType, {
2228+
name: name,
2229+
policyType: 'value',
2230+
fromWireType: (c) => c,
2231+
toWireType: (destructors, c) => c,
2232+
readValueFromPointer: enumReadValueFromPointer(name, size, isSigned),
2233+
destructorFunction: null,
2234+
});
2235+
2236+
exposePublicSymbol(name, keysMap);
2237+
// Just exposes a simple dict. argCount is meaningless here,
2238+
delete Module[name].argCount;
2239+
break;
2240+
}
2241+
case 'string': {
2242+
var valuesMap = {};
2243+
var reverseMap = {};
2244+
var keysMap = {};
2245+
2246+
registerType(rawType, {
2247+
name: name,
2248+
valuesMap,
2249+
reverseMap,
2250+
keysMap,
2251+
valueType,
2252+
fromWireType: function(c) {
2253+
return this.reverseMap[c];
2254+
},
2255+
toWireType: function(destructors, c) {
2256+
return this.valuesMap[c];
2257+
},
2258+
readValueFromPointer: enumReadValueFromPointer(name, size, isSigned),
2259+
destructorFunction: null,
2260+
});
2261+
2262+
exposePublicSymbol(name, keysMap);
2263+
// Just exposes a simple dict. argCount is meaningless here,
2264+
delete Module[name].argCount;
2265+
}
22412266
}
22422267
},
22432268

@@ -2246,18 +2271,26 @@ var LibraryEmbind = {
22462271
var enumType = requireRegisteredType(rawEnumType, 'enum');
22472272
name = AsciiToString(name);
22482273

2249-
if (enumType.asString) {
2250-
enumType.valuesMap[name] = enumValue;
2251-
enumType.reverseMap[enumValue] = name;
2252-
enumType.keysMap[name] = name;
2253-
} else {
2254-
var Enum = enumType.constructor;
2255-
var Value = Object.create(enumType.constructor.prototype, {
2256-
value: {value: enumValue},
2257-
constructor: {value: createNamedFunction(`${enumType.name}_${name}`, function() {})},
2258-
});
2259-
Enum.values[enumValue] = Value;
2260-
Enum[name] = Value;
2274+
switch (enumType.valueType) {
2275+
case 'object': {
2276+
var Enum = enumType.constructor;
2277+
var Value = Object.create(enumType.constructor.prototype, {
2278+
value: {value: enumValue},
2279+
constructor: {value: createNamedFunction(`${enumType.name}_${name}`, function() {})},
2280+
});
2281+
Enum.values[enumValue] = Value;
2282+
Enum[name] = Value;
2283+
break;
2284+
}
2285+
case 'number': {
2286+
enumType.keysMap[name] = enumValue;
2287+
break;
2288+
}
2289+
case 'string': {
2290+
enumType.valuesMap[name] = enumValue;
2291+
enumType.reverseMap[enumValue] = name;
2292+
enumType.keysMap[name] = name;
2293+
}
22612294
}
22622295
},
22632296

src/lib/libembind_gen.js

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -279,16 +279,16 @@ var LibraryEmbind = {
279279
},
280280
$EnumDefinition: class {
281281
hasPublicSymbol = true;
282-
constructor(typeId, name, asString) {
282+
constructor(typeId, name, valueType) {
283283
this.typeId = typeId;
284284
this.name = name;
285285
this.items = [];
286286
this.destructorType = 'none';
287-
this.asString = asString;
287+
this.valueType = valueType;
288288
}
289289

290290
print(nameMap, out) {
291-
if (!this.asString) {
291+
if (this.valueType === 'object') {
292292
out.push(`export interface ${this.name}Value<T extends number> {\n`);
293293
out.push(' value: T;\n}\n');
294294
}
@@ -298,10 +298,15 @@ var LibraryEmbind = {
298298
} else {
299299
const outItems = [];
300300
for (const [name, value] of this.items) {
301-
if (this.asString) {
302-
outItems.push(`'${name}'`);
303-
} else {
304-
outItems.push(`${this.name}Value<${value}>`);
301+
switch (this.valueType) {
302+
case 'object':
303+
outItems.push(`${this.name}Value<${value}>`);
304+
break;
305+
case 'string':
306+
outItems.push(`'${name}'`);
307+
break;
308+
case 'number':
309+
outItems.push(`${value}`);
305310
}
306311
}
307312
out.push(outItems.join('|'));
@@ -313,10 +318,15 @@ var LibraryEmbind = {
313318
out.push(` ${this.name}: {`);
314319
const outItems = [];
315320
for (const [name, value] of this.items) {
316-
if (this.asString) {
317-
outItems.push(`${name}: '${name}'`);
318-
} else {
319-
outItems.push(`${name}: ${this.name}Value<${value}>`);
321+
switch (this.valueType) {
322+
case 'object':
323+
outItems.push(`${name}: ${this.name}Value<${value}>`);
324+
break;
325+
case 'string':
326+
outItems.push(`${name}: '${name}'`);
327+
break;
328+
case 'number':
329+
outItems.push(`${name}: ${value}`);
320330
}
321331
}
322332
out.push(outItems.join(', '));
@@ -722,10 +732,11 @@ var LibraryEmbind = {
722732
},
723733
// Stub function. This is called a when extending an object and not needed for TS generation.
724734
_embind_create_inheriting_constructor: (constructorName, wrapperType, properties) => {},
725-
_embind_register_enum__deps: ['$AsciiToString', '$EnumDefinition', '$moduleDefinitions'],
726-
_embind_register_enum: function(rawType, name, size, isSigned, asString) {
735+
_embind_register_enum__deps: ['$AsciiToString', '$EnumDefinition', '$moduleDefinitions', '$getEnumValueType'],
736+
_embind_register_enum: function(rawType, name, size, isSigned, rawValueType) {
727737
name = AsciiToString(name);
728-
const enumDef = new EnumDefinition(rawType, name, asString);
738+
const valueType = getEnumValueType(rawValueType);
739+
const enumDef = new EnumDefinition(rawType, name, valueType);
729740
registerType(rawType, enumDef);
730741
moduleDefinitions.push(enumDef);
731742
},

src/lib/libembind_shared.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,11 @@ var LibraryEmbindShared = {
183183
}
184184
},
185185

186+
$getEnumValueType(rawValueType) {
187+
// This must match the values of enum_value_type in wire.h
188+
return rawValueType === 0 ? 'object' : (rawValueType === 1 ? 'number' : 'string');
189+
},
190+
186191
$getRequiredArgCount(argTypes) {
187192
var requiredArgCount = argTypes.length - 2;
188193
for (var i = argTypes.length - 1; i >= 2; --i) {

system/include/emscripten/bind.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ void _embind_register_enum(
225225
const char* name,
226226
size_t size,
227227
bool isSigned,
228-
bool asString);
228+
int policyValue);
229229

230230
void _embind_register_smart_ptr(
231231
TYPEID pointerType,
@@ -1990,14 +1990,14 @@ class enum_ {
19901990
public:
19911991
typedef EnumType enum_type;
19921992

1993-
enum_(const char* name, bool asString = false) {
1993+
enum_(const char* name, enum_value_type valueType = enum_value_type::object) {
19941994
using namespace internal;
19951995
_embind_register_enum(
19961996
internal::TypeID<EnumType>::get(),
19971997
name,
19981998
sizeof(EnumType),
19991999
std::is_signed<typename std::underlying_type<EnumType>::type>::value,
2000-
asString);
2000+
static_cast<int>(valueType));
20012001
}
20022002

20032003
enum_& value(const char* name, EnumType value) {

system/include/emscripten/wire.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,12 @@ struct reference : public allow_raw_pointers {};
577577

578578
} // end namespace return_value_policy
579579

580+
enum class enum_value_type {
581+
object = 0,
582+
number = 1,
583+
string = 2
584+
};
585+
580586
namespace internal {
581587

582588
#if __cplusplus >= 201703L

test/embind/embind_test.cpp

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1016,6 +1016,12 @@ EnumClass emval_test_take_and_return_EnumClass(EnumClass e) {
10161016
return e;
10171017
}
10181018

1019+
enum class EnumNum { ONE, TWO };
1020+
1021+
EnumNum emval_test_take_and_return_EnumNum(EnumNum e) {
1022+
return e;
1023+
}
1024+
10191025
enum class EnumStr { ONE, TWO };
10201026

10211027
EnumStr emval_test_take_and_return_EnumStr(EnumStr e) {
@@ -2356,7 +2362,14 @@ EMSCRIPTEN_BINDINGS(tests) {
23562362
;
23572363
function("emval_test_take_and_return_EnumClass", &emval_test_take_and_return_EnumClass);
23582364

2359-
enum_<EnumStr>("EnumStr", true)
2365+
enum_<EnumNum>("EnumNum", enum_value_type::number)
2366+
.value("ONE", EnumNum::ONE)
2367+
.value("TWO", EnumNum::TWO)
2368+
;
2369+
function("emval_test_take_and_return_EnumNum", &emval_test_take_and_return_EnumNum);
2370+
2371+
2372+
enum_<EnumStr>("EnumStr", enum_value_type::string)
23602373
.value("ONE", EnumStr::ONE)
23612374
.value("TWO", EnumStr::TWO)
23622375
;
@@ -2416,7 +2429,7 @@ EMSCRIPTEN_BINDINGS(tests) {
24162429

24172430
register_map<std::string, int>("StringIntMap");
24182431
function("embind_test_get_string_int_map", embind_test_get_string_int_map);
2419-
2432+
24202433
register_map<int, std::string, std::greater<int>>("IntStringMapGreater");
24212434
function("embind_test_get_int_string_greater_map", embind_test_get_int_string_greater_map);
24222435

0 commit comments

Comments
 (0)