Skip to content

Commit 92d9adb

Browse files
committed
refactor(embind): split enum and string_enum classes
1 parent 2414ef3 commit 92d9adb

File tree

9 files changed

+172
-69
lines changed

9 files changed

+172
-69
lines changed

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

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -804,12 +804,11 @@ 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)
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.
813812

814813

815814
.. cpp:function:: enum_& value(const char* name, EnumType value)
@@ -821,6 +820,37 @@ Enums
821820
:returns: A reference to the current object. This allows chaining of multiple enum values in the :cpp:func:`EMSCRIPTEN_BINDINGS` block.
822821

823822

823+
.. cpp:class:: string_enum_
824+
825+
.. code-block:: cpp
826+
827+
//prototype
828+
template<typename EnumType>
829+
class string_enum_
830+
831+
Registers an enum, just like :cpp:class:`enum_`, but here the values are represented as plain strings in JavaScript. This is closer to how typescript implements enums, and is handier for basic operations like comparison and serialization. See :ref:`embind-enums` for more information.
832+
833+
834+
.. cpp:type:: enum_type
835+
836+
A typedef of ``EnumType`` (a typename for the class).
837+
838+
839+
.. cpp:function:: string_enum_(const char* name)
840+
841+
Constructor.
842+
843+
:param const char* name:
844+
845+
846+
.. cpp:function:: string_enum_& value(const char* name, EnumType value)
847+
848+
Registers an enum value.
849+
850+
:param const char* name: The name of the enumerated value.
851+
:param EnumType value: The type of the enumerated value.
852+
:returns: A reference to the current object. This allows chaining of multiple enum values in the :cpp:func:`EMSCRIPTEN_BINDINGS` block.
853+
824854

825855
Constants
826856
=========

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -825,21 +825,27 @@ 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 alternatively use the :cpp:class:`string_enum_` class to register your enum values. In this case, they will be represented as plain strings in JavaScript. This is closer to how typescript implements enums, and is handier for basic operations like comparison and serialization.
829829

830830
.. code:: cpp
831831
832-
EMSCRIPTEN_BINDINGS(my_enum_example) {
833-
enum_<MyEnum>("MyEnum", true)
832+
EMSCRIPTEN_BINDINGS(my_string_enum_example) {
833+
string_enum_<MyEnum>("MyEnum")
834834
.value("ONE", MyEnum::ONE)
835835
.value("TWO", MyEnum::TWO)
836836
;
837837
}
838838
839+
You can still access enumeration values through a map registered in the Module, but you can also just use the value strings.
840+
839841
.. code:: javascript
840842
841843
Module.MyEnum.ONE === "ONE"; // true
842844
845+
const foo = function(enumValue) { /* ... */ };
846+
foo(Module.MyEnum.TWO);
847+
foo("TWO"); // also works
848+
843849
.. _embind-constants:
844850

845851
Constants

src/lib/libembind.js

Lines changed: 62 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -2199,68 +2199,77 @@ var LibraryEmbind = {
21992199
_embind_register_enum__docs: '/** @suppress {globalThis} */',
22002200
_embind_register_enum__deps: ['$exposePublicSymbol', '$enumReadValueFromPointer',
22012201
'$AsciiToString', '$registerType'],
2202-
_embind_register_enum: (rawType, name, size, isSigned, asString) => {
2202+
_embind_register_enum: (rawType, name, size, isSigned) => {
22032203
name = AsciiToString(name);
22042204

2205-
if (asString) {
2206-
var valuesMap = {};
2207-
var reverseMap = {};
2208-
var keysMap = {};
2209-
2210-
registerType(rawType, {
2211-
name: name,
2212-
valuesMap,
2213-
reverseMap,
2214-
keysMap,
2215-
asString,
2216-
fromWireType: function(c) {
2217-
return this.reverseMap[c];
2218-
},
2219-
toWireType: function(destructors, c) {
2220-
return this.valuesMap[c];
2221-
},
2222-
readValueFromPointer: enumReadValueFromPointer(name, size, isSigned),
2223-
destructorFunction: null,
2224-
});
2225-
exposePublicSymbol(name, keysMap);
2226-
// Just exposes a simple dict. argCount is meaningless here,
2227-
delete Module[name].argCount;
2228-
} else {
2229-
function ctor() {}
2230-
ctor.values = {};
2231-
2232-
registerType(rawType, {
2233-
name,
2234-
constructor: ctor,
2235-
fromWireType: function(c) {
2236-
return this.constructor.values[c];
2237-
},
2238-
toWireType: (destructors, c) => c.value,
2239-
readValueFromPointer: enumReadValueFromPointer(name, size, isSigned),
2240-
destructorFunction: null,
2241-
});
2242-
exposePublicSymbol(name, ctor);
2243-
}
2205+
function ctor() {}
2206+
ctor.values = {};
2207+
2208+
registerType(rawType, {
2209+
name,
2210+
constructor: ctor,
2211+
fromWireType: function(c) {
2212+
return this.constructor.values[c];
2213+
},
2214+
toWireType: (destructors, c) => c.value,
2215+
readValueFromPointer: enumReadValueFromPointer(name, size, isSigned),
2216+
destructorFunction: null,
2217+
});
2218+
exposePublicSymbol(name, ctor);
22442219
},
22452220

22462221
_embind_register_enum_value__deps: ['$createNamedFunction', '$AsciiToString', '$requireRegisteredType'],
22472222
_embind_register_enum_value: (rawEnumType, name, enumValue) => {
22482223
var enumType = requireRegisteredType(rawEnumType, 'enum');
22492224
name = AsciiToString(name);
22502225

2251-
if (enumType.asString) {
2252-
enumType.valuesMap[name] = enumValue;
2253-
enumType.reverseMap[enumValue] = name;
2254-
enumType.keysMap[name] = name;
2255-
} else {
2256-
var Enum = enumType.constructor;
2257-
var Value = Object.create(enumType.constructor.prototype, {
2258-
value: {value: enumValue},
2259-
constructor: {value: createNamedFunction(`${enumType.name}_${name}`, function() {})},
2260-
});
2261-
Enum.values[enumValue] = Value;
2262-
Enum[name] = Value;
2263-
}
2226+
var Enum = enumType.constructor;
2227+
var Value = Object.create(enumType.constructor.prototype, {
2228+
value: {value: enumValue},
2229+
constructor: {value: createNamedFunction(`${enumType.name}_${name}`, function() {})},
2230+
});
2231+
Enum.values[enumValue] = Value;
2232+
Enum[name] = Value;
2233+
},
2234+
2235+
_embind_register_string_enum__docs: '/** @suppress {globalThis} */',
2236+
_embind_register_string_enum__deps: ['$exposePublicSymbol', '$enumReadValueFromPointer',
2237+
'$AsciiToString', '$registerType'],
2238+
_embind_register_string_enum: (rawType, name, size, isSigned) => {
2239+
name = AsciiToString(name);
2240+
2241+
// Use a two way map to enhance speed of conversions.
2242+
var valuesMap = {}; // "Name" -> 1
2243+
var reverseMap = {}; // 1 -> "Name"
2244+
// Only expose the names to Js.
2245+
var keysMap = {}; // "Name" -> "Name"
2246+
2247+
registerType(rawType, {
2248+
name: name,
2249+
valuesMap,
2250+
reverseMap,
2251+
keysMap,
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+
exposePublicSymbol(name, keysMap);
2262+
// Just exposes a simple dict. argCount is meaningless here,
2263+
delete Module[name].argCount;
2264+
},
2265+
2266+
_embind_register_string_enum_value__deps: ['$AsciiToString', '$requireRegisteredType'],
2267+
_embind_register_string_enum_value: (rawEnumType, name, enumValue) => {
2268+
var enumType = requireRegisteredType(rawEnumType, 'string_enum');
2269+
name = AsciiToString(name);
2270+
enumType.valuesMap[name] = enumValue;
2271+
enumType.reverseMap[enumValue] = name;
2272+
enumType.keysMap[name] = name;
22642273
},
22652274

22662275
_embind_register_constant__deps: ['$AsciiToString', '$whenDependentTypesAreResolved'],

src/lib/libembind_gen.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -723,9 +723,9 @@ var LibraryEmbind = {
723723
// Stub function. This is called a when extending an object and not needed for TS generation.
724724
_embind_create_inheriting_constructor: (constructorName, wrapperType, properties) => {},
725725
_embind_register_enum__deps: ['$AsciiToString', '$EnumDefinition', '$moduleDefinitions'],
726-
_embind_register_enum: function(rawType, name, size, isSigned, asString) {
726+
_embind_register_enum: function(rawType, name, size, isSigned) {
727727
name = AsciiToString(name);
728-
const enumDef = new EnumDefinition(rawType, name, asString);
728+
const enumDef = new EnumDefinition(rawType, name, false);
729729
registerType(rawType, enumDef);
730730
moduleDefinitions.push(enumDef);
731731
},
@@ -735,6 +735,20 @@ var LibraryEmbind = {
735735
const enumDef = requireRegisteredType(rawEnumType, name);
736736
enumDef.items.push([name, enumValue]);
737737
},
738+
// String enums share almost the same structure as regular enums
739+
_embind_register_string_enum__deps: ['$AsciiToString', '$EnumDefinition', '$moduleDefinitions'],
740+
_embind_register_string_enum: function(rawType, name, size, isSigned) {
741+
name = AsciiToString(name);
742+
const enumDef = new EnumDefinition(rawType, name, true);
743+
registerType(rawType, enumDef);
744+
moduleDefinitions.push(enumDef);
745+
},
746+
_embind_register_string_enum_value__deps: ['$AsciiToString', '$requireRegisteredType'],
747+
_embind_register_string_enum_value: function(rawEnumType, name, enumValue) {
748+
name = AsciiToString(name);
749+
const enumDef = requireRegisteredType(rawEnumType, name);
750+
enumDef.items.push([name, enumValue]);
751+
},
738752
_embind_register_constant__deps: ['$AsciiToString', '$ConstantDefinition', '$whenDependentTypesAreResolved', '$moduleDefinitions'],
739753
_embind_register_constant: function(name, typeId, value) {
740754
name = AsciiToString(name);

src/lib/libsigs.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ sigs = {
296296
_embind_register_class_property__sig: 'vpppppppppp',
297297
_embind_register_constant__sig: 'vppd',
298298
_embind_register_emval__sig: 'vp',
299-
_embind_register_enum__sig: 'vpppii',
299+
_embind_register_enum__sig: 'vpppi',
300300
_embind_register_enum_value__sig: 'vppi',
301301
_embind_register_float__sig: 'vppp',
302302
_embind_register_function__sig: 'vpippppii',
@@ -306,6 +306,8 @@ sigs = {
306306
_embind_register_smart_ptr__sig: 'vpppipppppppp',
307307
_embind_register_std_string__sig: 'vpp',
308308
_embind_register_std_wstring__sig: 'vppp',
309+
_embind_register_string_enum__sig: 'vpppi',
310+
_embind_register_string_enum_value__sig: 'vppi',
309311
_embind_register_user_type__sig: 'vpp',
310312
_embind_register_value_array__sig: 'vpppppp',
311313
_embind_register_value_array_element__sig: 'vppppppppp',

system/include/emscripten/bind.h

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -228,8 +228,13 @@ void _embind_register_enum(
228228
TYPEID enumType,
229229
const char* name,
230230
size_t size,
231-
bool isSigned,
232-
bool asString);
231+
bool isSigned);
232+
233+
void _embind_register_string_enum(
234+
TYPEID enumType,
235+
const char* name,
236+
size_t size,
237+
bool isSigned);
233238

234239
void _embind_register_smart_ptr(
235240
TYPEID pointerType,
@@ -250,6 +255,11 @@ void _embind_register_enum_value(
250255
const char* valueName,
251256
GenericEnumValue value);
252257

258+
void _embind_register_string_enum_value(
259+
TYPEID enumType,
260+
const char* valueName,
261+
GenericEnumValue value);
262+
253263
void _embind_register_constant(
254264
const char* name,
255265
TYPEID constantType,
@@ -2175,8 +2185,7 @@ class enum_ {
21752185
internal::TypeID<EnumType>::get(),
21762186
name,
21772187
sizeof(EnumType),
2178-
std::is_signed<typename std::underlying_type<EnumType>::type>::value,
2179-
asString);
2188+
std::is_signed<typename std::underlying_type<EnumType>::type>::value);
21802189
}
21812190

21822191
enum_& value(const char* name, EnumType value) {
@@ -2193,6 +2202,39 @@ class enum_ {
21932202
}
21942203
};
21952204

2205+
////////////////////////////////////////////////////////////////////////////////
2206+
// STRING ENUMS
2207+
////////////////////////////////////////////////////////////////////////////////
2208+
2209+
template<typename EnumType>
2210+
class string_enum_ {
2211+
public:
2212+
typedef EnumType enum_type;
2213+
2214+
string_enum_(const char* name) {
2215+
using namespace internal;
2216+
_embind_register_string_enum(
2217+
internal::TypeID<EnumType>::get(),
2218+
name,
2219+
sizeof(EnumType),
2220+
std::is_signed<typename std::underlying_type<EnumType>::type>::value);
2221+
}
2222+
2223+
string_enum_& value(const char* name, EnumType value) {
2224+
using namespace internal;
2225+
// TODO: there's still an issue here.
2226+
// if EnumType is an unsigned long, then JS may receive it as a signed long
2227+
static_assert(sizeof(value) <= sizeof(internal::GenericEnumValue), "enum type must fit in a GenericEnumValue");
2228+
2229+
_embind_register_string_enum_value(
2230+
internal::TypeID<EnumType>::get(),
2231+
name,
2232+
static_cast<internal::GenericEnumValue>(value));
2233+
return *this;
2234+
}
2235+
};
2236+
2237+
21962238
////////////////////////////////////////////////////////////////////////////////
21972239
// CONSTANTS
21982240
////////////////////////////////////////////////////////////////////////////////

test/embind/embind_test.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2356,7 +2356,7 @@ EMSCRIPTEN_BINDINGS(tests) {
23562356
;
23572357
function("emval_test_take_and_return_EnumClass", &emval_test_take_and_return_EnumClass);
23582358

2359-
enum_<EnumStr>("EnumStr", true)
2359+
string_enum_<EnumStr>("EnumStr")
23602360
.value("ONE", EnumStr::ONE)
23612361
.value("TWO", EnumStr::TWO)
23622362
;
@@ -2416,7 +2416,7 @@ EMSCRIPTEN_BINDINGS(tests) {
24162416

24172417
register_map<std::string, int>("StringIntMap");
24182418
function("embind_test_get_string_int_map", embind_test_get_string_int_map);
2419-
2419+
24202420
register_map<int, std::string, std::greater<int>>("IntStringMapGreater");
24212421
function("embind_test_get_int_string_greater_map", embind_test_get_int_string_greater_map);
24222422

test/other/embind_tsgen.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ EMSCRIPTEN_BINDINGS(Test) {
186186
.value("valueOne", Bar::kValueOne)
187187
.value("valueTwo", Bar::kValueTwo)
188188
.value("valueThree", Bar::kValueThree);
189-
enum_<Baz>("Baz", true)
189+
string_enum_<Baz>("Baz")
190190
.value("valueA", Baz::kValueA)
191191
.value("valueB", Baz::kValueB)
192192
.value("valueC", Baz::kValueC);

test/test_other.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15056,7 +15056,7 @@ def test_embind_negative_enum_values(self):
1505615056
.value("neg", value::neg)
1505715057
.value("zero", value::zero)
1505815058
.value("pos", value::pos);
15059-
enum_<otherValue>("valueStr", true)
15059+
string_enum_<otherValue>("valueStr")
1506015060
.value("neg", otherValue::neg)
1506115061
.value("zero", otherValue::zero)
1506215062
.value("pos", otherValue::pos);

0 commit comments

Comments
 (0)