Skip to content

Commit f6f516c

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

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
@@ -2197,38 +2197,68 @@ var LibraryEmbind = {
21972197
_embind_register_enum__docs: '/** @suppress {globalThis} */',
21982198
_embind_register_enum__deps: ['$exposePublicSymbol', '$enumReadValueFromPointer',
21992199
'$AsciiToString', '$registerType'],
2200-
_embind_register_enum: (rawType, name, size, isSigned) => {
2200+
_embind_register_enum: (rawType, name, size, isSigned, asString) => {
22012201
name = AsciiToString(name);
22022202

2203-
function ctor() {}
2204-
ctor.values = {};
2205-
2206-
registerType(rawType, {
2207-
name,
2208-
constructor: ctor,
2209-
fromWireType: function(c) {
2210-
return this.constructor.values[c];
2211-
},
2212-
toWireType: (destructors, c) => c.value,
2213-
readValueFromPointer: enumReadValueFromPointer(name, size, isSigned),
2214-
destructorFunction: null,
2215-
});
2216-
exposePublicSymbol(name, ctor);
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);
2241+
}
22172242
},
22182243

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

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

22342264
_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
@@ -224,7 +224,8 @@ void _embind_register_enum(
224224
TYPEID enumType,
225225
const char* name,
226226
size_t size,
227-
bool isSigned);
227+
bool isSigned,
228+
bool asString);
228229

229230
void _embind_register_smart_ptr(
230231
TYPEID pointerType,
@@ -1989,13 +1990,14 @@ class enum_ {
19891990
public:
19901991
typedef EnumType enum_type;
19911992

1992-
enum_(const char* name) {
1993+
enum_(const char* name, bool asString = false) {
19931994
using namespace internal;
19941995
_embind_register_enum(
19951996
internal::TypeID<EnumType>::get(),
19961997
name,
19971998
sizeof(EnumType),
1998-
std::is_signed<typename std::underlying_type<EnumType>::type>::value);
1999+
std::is_signed<typename std::underlying_type<EnumType>::type>::value,
2000+
asString);
19992001
}
20002002

20012003
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()) {}
@@ -185,9 +190,14 @@ EMSCRIPTEN_BINDINGS(Test) {
185190
.value("valueOne", Bar::kValueOne)
186191
.value("valueTwo", Bar::kValueTwo)
187192
.value("valueThree", Bar::kValueThree);
193+
enum_<Baz>("Baz", true)
194+
.value("valueA", Baz::kValueA)
195+
.value("valueB", Baz::kValueB)
196+
.value("valueC", Baz::kValueC);
188197
enum_<EmptyEnum>("EmptyEnum");
189198

190199
function("enum_returning_fn", &enum_returning_fn);
200+
function("str_enum_returning_fn", &str_enum_returning_fn);
191201

192202
value_array<ValArr>("ValArr")
193203
.element(&ValArr::x)
@@ -203,6 +213,7 @@ EMSCRIPTEN_BINDINGS(Test) {
203213
value_object<ValObj>("ValObj")
204214
.field("string", &ValObj::string)
205215
.field("bar", &ValObj::bar)
216+
.field("baz", &ValObj::baz)
206217
.field("callback", &ValObj::callback);
207218
function("getValObj", &getValObj);
208219
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)