Skip to content

Commit fad3ab8

Browse files
committed
feat(embind): add a way to register enums valus as plain string (#24324, #19387, #18585)
1 parent 1c1baae commit fad3ab8

File tree

16 files changed

+180
-36
lines changed

16 files changed

+180
-36
lines changed

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

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

806806

807-
.. cpp:function:: enum_(const char* name)
807+
.. cpp:function:: enum_(const char* name, bool asString = false)
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.
812813

813814

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

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -825,6 +825,21 @@ 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.
829+
830+
.. code:: cpp
831+
832+
EMSCRIPTEN_BINDINGS(my_enum_example) {
833+
enum_<MyEnum>("MyEnum", true)
834+
.value("ONE", MyEnum::ONE)
835+
.value("TWO", MyEnum::TWO)
836+
;
837+
}
838+
839+
.. code:: javascript
840+
841+
Module.MyEnum.ONE === "ONE"; // true
842+
828843
.. _embind-constants:
829844

830845
Constants

src/lib/libembind.js

Lines changed: 53 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2199,38 +2199,68 @@ 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) => {
2202+
_embind_register_enum: (rawType, name, size, isSigned, asString) => {
22032203
name = AsciiToString(name);
22042204

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);
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+
}
22192244
},
22202245

22212246
_embind_register_enum_value__deps: ['$createNamedFunction', '$AsciiToString', '$requireRegisteredType'],
22222247
_embind_register_enum_value: (rawEnumType, name, enumValue) => {
22232248
var enumType = requireRegisteredType(rawEnumType, 'enum');
22242249
name = AsciiToString(name);
22252250

2226-
var Enum = enumType.constructor;
2227-
2228-
var Value = Object.create(enumType.constructor.prototype, {
2229-
value: {value: enumValue},
2230-
constructor: {value: createNamedFunction(`${enumType.name}_${name}`, function() {})},
2231-
});
2232-
Enum.values[enumValue] = Value;
2233-
Enum[name] = Value;
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+
}
22342264
},
22352265

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

src/lib/libembind_gen.js

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

289290
print(nameMap, out) {
290-
out.push(`export interface ${this.name}Value<T extends number> {\n`);
291-
out.push(' value: T;\n}\n');
291+
if (!this.asString) {
292+
out.push(`export interface ${this.name}Value<T extends number> {\n`);
293+
out.push(' value: T;\n}\n');
294+
}
292295
out.push(`export type ${this.name} = `);
293296
if (this.items.length === 0) {
294297
out.push('never/* Empty Enumerator */');
295298
} else {
296299
const outItems = [];
297300
for (const [name, value] of this.items) {
298-
outItems.push(`${this.name}Value<${value}>`);
301+
if (this.asString) {
302+
outItems.push(`'${name}'`);
303+
} else {
304+
outItems.push(`${this.name}Value<${value}>`);
305+
}
299306
}
300307
out.push(outItems.join('|'));
301308
}
@@ -306,7 +313,11 @@ var LibraryEmbind = {
306313
out.push(` ${this.name}: {`);
307314
const outItems = [];
308315
for (const [name, value] of this.items) {
309-
outItems.push(`${name}: ${this.name}Value<${value}>`);
316+
if (this.asString) {
317+
outItems.push(`${name}: '${name}'`);
318+
} else {
319+
outItems.push(`${name}: ${this.name}Value<${value}>`);
320+
}
310321
}
311322
out.push(outItems.join(', '));
312323
out.push('};\n');
@@ -712,9 +723,9 @@ var LibraryEmbind = {
712723
// Stub function. This is called a when extending an object and not needed for TS generation.
713724
_embind_create_inheriting_constructor: (constructorName, wrapperType, properties) => {},
714725
_embind_register_enum__deps: ['$AsciiToString', '$EnumDefinition', '$moduleDefinitions'],
715-
_embind_register_enum: function(rawType, name, size, isSigned) {
726+
_embind_register_enum: function(rawType, name, size, isSigned, asString) {
716727
name = AsciiToString(name);
717-
const enumDef = new EnumDefinition(rawType, name);
728+
const enumDef = new EnumDefinition(rawType, name, asString);
718729
registerType(rawType, enumDef);
719730
moduleDefinitions.push(enumDef);
720731
},

src/lib/libsigs.js

Lines changed: 1 addition & 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: 'vpppi',
299+
_embind_register_enum__sig: 'vpppii',
300300
_embind_register_enum_value__sig: 'vppi',
301301
_embind_register_float__sig: 'vppp',
302302
_embind_register_function__sig: 'vpippppii',

system/include/emscripten/bind.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,8 @@ void _embind_register_enum(
228228
TYPEID enumType,
229229
const char* name,
230230
size_t size,
231-
bool isSigned);
231+
bool isSigned,
232+
bool asString);
232233

233234
void _embind_register_smart_ptr(
234235
TYPEID pointerType,
@@ -2168,13 +2169,14 @@ class enum_ {
21682169
public:
21692170
typedef EnumType enum_type;
21702171

2171-
enum_(const char* name) {
2172+
enum_(const char* name, bool asString = false) {
21722173
using namespace internal;
21732174
_embind_register_enum(
21742175
internal::TypeID<EnumType>::get(),
21752176
name,
21762177
sizeof(EnumType),
2177-
std::is_signed<typename std::underlying_type<EnumType>::type>::value);
2178+
std::is_signed<typename std::underlying_type<EnumType>::type>::value,
2179+
asString);
21782180
}
21792181

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

test/embind/embind.test.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2031,6 +2031,26 @@ module({
20312031
});
20322032
});
20332033

2034+
BaseFixture.extend("enums with string values", function() {
2035+
test("can compare enumeration values", function() {
2036+
assert.equal(cm.EnumStr.ONE, cm.EnumStr.ONE);
2037+
assert.equal(cm.EnumStr.ONE, 'ONE');
2038+
assert.notEqual(cm.EnumStr.ONE, cm.EnumStr.TWO);
2039+
});
2040+
2041+
if (typeof INVOKED_FROM_EMSCRIPTEN_TEST_RUNNER === "undefined") { // TODO: Enable this to work in Emscripten runner as well!
2042+
test("repr includes enum value", function() {
2043+
assert.equal('ONE', IMVU.repr(cm.EnumStr.ONE));
2044+
assert.equal('TWO', IMVU.repr(cm.EnumStr.TWO));
2045+
});
2046+
}
2047+
2048+
test("can pass and return enumeration values to functions", function() {
2049+
assert.equal(cm.EnumStr.TWO, cm.emval_test_take_and_return_EnumStr(cm.EnumStr.TWO));
2050+
assert.equal('TWO', cm.emval_test_take_and_return_EnumStr('TWO'));
2051+
});
2052+
});
2053+
20342054
BaseFixture.extend("emval call tests", function() {
20352055
test("can call functions from C++", function() {
20362056
var called = false;

test/embind/embind_test.cpp

Lines changed: 12 additions & 0 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 EnumStr { ONE, TWO };
1020+
1021+
EnumStr emval_test_take_and_return_EnumStr(EnumStr e) {
1022+
return e;
1023+
}
1024+
10191025
void emval_test_call_function(val v, int i, float f, TupleVector tv, StructVector sv) {
10201026
v(i, f, tv, sv);
10211027
}
@@ -2350,6 +2356,12 @@ EMSCRIPTEN_BINDINGS(tests) {
23502356
;
23512357
function("emval_test_take_and_return_EnumClass", &emval_test_take_and_return_EnumClass);
23522358

2359+
enum_<EnumStr>("EnumStr", true)
2360+
.value("ONE", EnumStr::ONE)
2361+
.value("TWO", EnumStr::TWO)
2362+
;
2363+
function("emval_test_take_and_return_EnumStr", &emval_test_take_and_return_EnumStr);
2364+
23532365
function("emval_test_call_function", &emval_test_call_function);
23542366

23552367
function("emval_test_return_unique_ptr", &emval_test_return_unique_ptr);

test/other/embind_tsgen.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,14 @@ std::unique_ptr<Test> class_unique_ptr_returning_fn() {
4747

4848
enum Bar { kValueOne, kValueTwo, kValueThree };
4949

50+
enum Baz { kValueA, kValueB, kValueC };
51+
5052
enum EmptyEnum {};
5153

5254
Bar enum_returning_fn() { return kValueOne; }
5355

56+
Baz str_enum_returning_fn() { return kValueA; }
57+
5458
struct ValArr {
5559
int x, y, z;
5660
};
@@ -59,6 +63,7 @@ EMSCRIPTEN_DECLARE_VAL_TYPE(CallbackType);
5963

6064
struct ValObj {
6165
Bar bar;
66+
Baz baz;
6267
std::string string;
6368
CallbackType callback;
6469
ValObj() : callback(val::undefined()) {}
@@ -181,9 +186,14 @@ EMSCRIPTEN_BINDINGS(Test) {
181186
.value("valueOne", Bar::kValueOne)
182187
.value("valueTwo", Bar::kValueTwo)
183188
.value("valueThree", Bar::kValueThree);
189+
enum_<Baz>("Baz", true)
190+
.value("valueA", Baz::kValueA)
191+
.value("valueB", Baz::kValueB)
192+
.value("valueC", Baz::kValueC);
184193
enum_<EmptyEnum>("EmptyEnum");
185194

186195
function("enum_returning_fn", &enum_returning_fn);
196+
function("str_enum_returning_fn", &str_enum_returning_fn);
187197

188198
value_array<ValArr>("ValArr")
189199
.element(&ValArr::x)
@@ -199,6 +209,7 @@ EMSCRIPTEN_BINDINGS(Test) {
199209
value_object<ValObj>("ValObj")
200210
.field("string", &ValObj::string)
201211
.field("bar", &ValObj::bar)
212+
.field("baz", &ValObj::baz)
202213
.field("callback", &ValObj::callback);
203214
function("getValObj", &getValObj);
204215
function("setValObj", &setValObj);

test/other/embind_tsgen.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ export interface BarValue<T extends number> {
3636
}
3737
export type Bar = BarValue<0>|BarValue<1>|BarValue<2>;
3838

39+
export type Baz = 'valueA'|'valueB'|'valueC';
40+
3941
export interface EmptyEnumValue<T extends number> {
4042
value: T;
4143
}
@@ -94,6 +96,7 @@ export type ValArr = [ number, number, number ];
9496
export type ValObj = {
9597
string: EmbindString,
9698
bar: Bar,
99+
baz: Baz,
97100
callback: (message: string) => void
98101
};
99102

@@ -113,8 +116,10 @@ interface EmbindModule {
113116
a_class_instance: Test;
114117
an_enum: Bar;
115118
Bar: {valueOne: BarValue<0>, valueTwo: BarValue<1>, valueThree: BarValue<2>};
119+
Baz: {valueA: 'valueA', valueB: 'valueB', valueC: 'valueC'};
116120
EmptyEnum: {};
117121
enum_returning_fn(): Bar;
122+
str_enum_returning_fn(): Baz;
118123
IntVec: {
119124
new(): IntVec;
120125
};

0 commit comments

Comments
 (0)