Skip to content

Commit 2c2feca

Browse files
committed
feat(embind): add a way to register enums as their plain value or as their props name (#24324, #19387, #18585)
1 parent 1967514 commit 2c2feca

File tree

18 files changed

+345
-83
lines changed

18 files changed

+345
-83
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, enum_value_type valueType = enum_value_type::object)
808808

809809
Constructor.
810810

811811
:param const char* name:
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.
812813

813814

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

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -825,6 +825,33 @@ type.
825825
Module.OldStyle.ONE;
826826
Module.NewStyle.TWO;
827827
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.
831+
832+
.. code:: cpp
833+
834+
EMSCRIPTEN_BINDINGS(my_enum_example) {
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)
846+
;
847+
}
848+
849+
.. code:: javascript
850+
851+
Module.ObjectEnum.ONE.value === 1;
852+
Module.NumberEnum.ONE === 1;
853+
Module.StringEnum.ONE === "ONE";
854+
828855
.. _embind-constants:
829856

830857
Constants

src/lib/libembind.js

Lines changed: 86 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2196,39 +2196,102 @@ 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) => {
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-
function ctor() {}
2204-
ctor.values = {};
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+
});
22052235

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);
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+
}
2266+
}
22172267
},
22182268

22192269
_embind_register_enum_value__deps: ['$createNamedFunction', '$AsciiToString', '$requireRegisteredType'],
22202270
_embind_register_enum_value: (rawEnumType, name, enumValue) => {
22212271
var enumType = requireRegisteredType(rawEnumType, 'enum');
22222272
name = AsciiToString(name);
22232273

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;
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+
}
2294+
}
22322295
},
22332296

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

src/lib/libembind_gen.js

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -279,23 +279,35 @@ var LibraryEmbind = {
279279
},
280280
$EnumDefinition: class {
281281
hasPublicSymbol = true;
282-
constructor(typeId, name) {
282+
constructor(typeId, name, valueType) {
283283
this.typeId = typeId;
284284
this.name = name;
285285
this.items = [];
286286
this.destructorType = 'none';
287+
this.valueType = valueType;
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.valueType === 'object') {
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+
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}`);
310+
}
299311
}
300312
out.push(outItems.join('|'));
301313
}
@@ -306,7 +318,16 @@ var LibraryEmbind = {
306318
out.push(` ${this.name}: {`);
307319
const outItems = [];
308320
for (const [name, value] of this.items) {
309-
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}`);
330+
}
310331
}
311332
out.push(outItems.join(', '));
312333
out.push('};\n');
@@ -711,10 +732,11 @@ var LibraryEmbind = {
711732
},
712733
// Stub function. This is called a when extending an object and not needed for TS generation.
713734
_embind_create_inheriting_constructor: (constructorName, wrapperType, properties) => {},
714-
_embind_register_enum__deps: ['$AsciiToString', '$EnumDefinition', '$moduleDefinitions'],
715-
_embind_register_enum: function(rawType, name, size, isSigned) {
735+
_embind_register_enum__deps: ['$AsciiToString', '$EnumDefinition', '$moduleDefinitions', '$getEnumValueType'],
736+
_embind_register_enum: function(rawType, name, size, isSigned, rawValueType) {
716737
name = AsciiToString(name);
717-
const enumDef = new EnumDefinition(rawType, name);
738+
const valueType = getEnumValueType(rawValueType);
739+
const enumDef = new EnumDefinition(rawType, name, valueType);
718740
registerType(rawType, enumDef);
719741
moduleDefinitions.push(enumDef);
720742
},

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) {

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+
int policyValue);
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, enum_value_type valueType = enum_value_type::object) {
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+
static_cast<int>(valueType));
19992001
}
20002002

20012003
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.js

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

2034+
BaseFixture.extend("enums with integer values", function() {
2035+
test("can compare enumeration values", function() {
2036+
assert.equal(cm.EnumNum.ONE, cm.EnumNum.ONE);
2037+
assert.equal(cm.EnumNum.ONE, 0);
2038+
assert.notEqual(cm.EnumNum.TWO, cm.EnumNum.ONE);
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(0, IMVU.repr(cm.EnumNum.ONE));
2044+
assert.equal(1, IMVU.repr(cm.EnumNum.TWO));
2045+
});
2046+
}
2047+
2048+
test("can pass and return enumeration values to functions", function() {
2049+
assert.equal(cm.EnumNum.TWO, cm.emval_test_take_and_return_EnumNum(cm.EnumNum.TWO));
2050+
assert.equal(cm.EnumNum.TWO, cm.emval_test_take_and_return_EnumNum(cm.EnumNum.TWO));
2051+
});
2052+
});
2053+
2054+
2055+
BaseFixture.extend("enums with string values", function() {
2056+
test("can compare enumeration values", function() {
2057+
assert.equal(cm.EnumStr.ONE, cm.EnumStr.ONE);
2058+
assert.equal(cm.EnumStr.ONE, 'ONE');
2059+
assert.notEqual(cm.EnumStr.ONE, cm.EnumStr.TWO);
2060+
});
2061+
2062+
if (typeof INVOKED_FROM_EMSCRIPTEN_TEST_RUNNER === "undefined") { // TODO: Enable this to work in Emscripten runner as well!
2063+
test("repr includes enum value", function() {
2064+
assert.equal('ONE', IMVU.repr(cm.EnumStr.ONE));
2065+
assert.equal('TWO', IMVU.repr(cm.EnumStr.TWO));
2066+
});
2067+
}
2068+
2069+
test("can pass and return enumeration values to functions", function() {
2070+
assert.equal(cm.EnumStr.TWO, cm.emval_test_take_and_return_EnumStr(cm.EnumStr.TWO));
2071+
assert.equal('TWO', cm.emval_test_take_and_return_EnumStr('TWO'));
2072+
});
2073+
});
2074+
20342075
BaseFixture.extend("emval call tests", function() {
20352076
test("can call functions from C++", function() {
20362077
var called = false;

0 commit comments

Comments
 (0)