Skip to content

Commit 31757f4

Browse files
committed
[embind] Add iterable protocol support for bound classes
Add `class_<T>::iterable()` to implement `Symbol.iterator`. Use this in `register_vector` so bound vectors have better ergonomics, such as working with `for...of` and `Array.from()`. This is tangentially related to #11070.
1 parent 8c5cdc8 commit 31757f4

File tree

11 files changed

+196
-8
lines changed

11 files changed

+196
-8
lines changed

src/lib/libembind.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,39 @@ var LibraryEmbind = {
421421
return this.fromWireType({{{ makeGetValue('pointer', '0', '*') }}});
422422
},
423423

424+
$installIndexedIterator: (proto, sizeName, getName) => {
425+
if (typeof Symbol === 'undefined' || !Symbol.iterator) {
426+
return;
427+
}
428+
429+
const makeIterator = (size, getValue) => {
430+
const useBigInt = typeof size === 'bigint';
431+
const one = useBigInt ? 1n : 1;
432+
let index = useBigInt ? 0n : 0;
433+
return {
434+
next() {
435+
if (index >= size) {
436+
return { done: true };
437+
}
438+
const current = index;
439+
index += one;
440+
const value = getValue(current);
441+
return { value, done: false };
442+
},
443+
[Symbol.iterator]() {
444+
return this;
445+
},
446+
};
447+
};
448+
449+
if (!proto[Symbol.iterator]) {
450+
proto[Symbol.iterator] = function() {
451+
const size = this[sizeName]();
452+
return makeIterator(size, (i) => this[getName](i));
453+
};
454+
}
455+
},
456+
424457
_embind_register_std_string__deps: [
425458
'$AsciiToString', '$registerType',
426459
'$readPointer', '$throwBindingError',
@@ -1723,6 +1756,19 @@ var LibraryEmbind = {
17231756
);
17241757
},
17251758

1759+
_embind_register_iterable__deps: [
1760+
'$whenDependentTypesAreResolved', '$installIndexedIterator', '$AsciiToString',
1761+
],
1762+
_embind_register_iterable: (rawClassType, rawElementType, sizeName, getName) => {
1763+
sizeName = AsciiToString(sizeName);
1764+
getName = AsciiToString(getName);
1765+
whenDependentTypesAreResolved([], [rawClassType, rawElementType], (types) => {
1766+
const classType = types[0];
1767+
installIndexedIterator(classType.registeredClass.instancePrototype, sizeName, getName);
1768+
return [];
1769+
});
1770+
},
1771+
17261772
_embind_register_class_constructor__deps: [
17271773
'$heap32VectorToArray', '$embind__requireFunction',
17281774
'$whenDependentTypesAreResolved',

src/lib/libembind_gen.js

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ var LibraryEmbind = {
177177
this.constructors = [];
178178
this.base = base;
179179
this.properties = [];
180+
this.iterableElementType = null;
180181
this.destructorType = 'none';
181182
if (base) {
182183
this.destructorType = 'stack';
@@ -185,11 +186,16 @@ var LibraryEmbind = {
185186

186187
print(nameMap, out) {
187188
out.push(`export interface ${this.name}`);
189+
const extendsParts = [];
188190
if (this.base) {
189-
out.push(` extends ${this.base.name}`);
191+
extendsParts.push(this.base.name);
190192
} else {
191-
out.push(' extends ClassHandle');
193+
extendsParts.push('ClassHandle');
192194
}
195+
if (this.iterableElementType) {
196+
extendsParts.push(`Iterable<${nameMap(this.iterableElementType, true)}>`);
197+
}
198+
out.push(` extends ${extendsParts.join(', ')}`);
193199
out.push(' {\n');
194200
for (const property of this.properties) {
195201
const props = [];
@@ -629,6 +635,15 @@ var LibraryEmbind = {
629635
);
630636

631637
},
638+
_embind_register_iterable__deps: ['$whenDependentTypesAreResolved'],
639+
_embind_register_iterable: (rawClassType, rawElementType, sizeName, getName) => {
640+
whenDependentTypesAreResolved([], [rawClassType, rawElementType], (types) => {
641+
const classType = types[0];
642+
const elementType = types[1];
643+
classType.iterableElementType = elementType;
644+
return [];
645+
});
646+
},
632647
_embind_register_class_constructor__deps: ['$whenDependentTypesAreResolved', '$createFunctionDefinition'],
633648
_embind_register_class_constructor: function(
634649
rawClassType,

system/include/emscripten/bind.h

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,12 @@ void _embind_register_class_class_property(
217217
const char* setterSignature,
218218
GenericFunction setter);
219219

220+
void _embind_register_iterable(
221+
TYPEID classType,
222+
TYPEID elementType,
223+
const char* sizeName,
224+
const char* getName);
225+
220226
EM_VAL _embind_create_inheriting_constructor(
221227
const char* constructorName,
222228
TYPEID wrapperType,
@@ -1586,6 +1592,19 @@ class class_ {
15861592
return *this;
15871593
}
15881594

1595+
template<typename ElementType>
1596+
EMSCRIPTEN_ALWAYS_INLINE const class_& iterable(
1597+
const char* sizeName,
1598+
const char* getName) const {
1599+
using namespace internal;
1600+
_embind_register_iterable(
1601+
TypeID<ClassType>::get(),
1602+
TypeID<ElementType>::get(),
1603+
sizeName,
1604+
getName);
1605+
return *this;
1606+
}
1607+
15891608
template<
15901609
typename FieldType,
15911610
typename... Policies,
@@ -1846,6 +1865,8 @@ template<typename T, class Allocator=std::allocator<T>>
18461865
class_<std::vector<T, Allocator>> register_vector(const char* name) {
18471866
typedef std::vector<T, Allocator> VecType;
18481867
register_optional<T>();
1868+
using VectorElementType =
1869+
typename internal::RawPointerTransformer<T, std::is_pointer<T>::value>::type;
18491870

18501871
return class_<VecType>(name)
18511872
.template constructor<>()
@@ -1854,7 +1875,7 @@ class_<std::vector<T, Allocator>> register_vector(const char* name) {
18541875
.function("size", internal::VectorAccess<VecType>::size, allow_raw_pointers())
18551876
.function("get", internal::VectorAccess<VecType>::get, allow_raw_pointers())
18561877
.function("set", internal::VectorAccess<VecType>::set, allow_raw_pointers())
1857-
;
1878+
.template iterable<VectorElementType>("size", "get");
18581879
}
18591880

18601881
////////////////////////////////////////////////////////////////////////////////

test/embind/embind.test.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1141,6 +1141,34 @@ module({
11411141
small.delete();
11421142
vec.delete();
11431143
});
1144+
1145+
test("std::vector is iterable", function() {
1146+
if (typeof Symbol === "undefined" || !Symbol.iterator) {
1147+
return;
1148+
}
1149+
var vec = cm.emval_test_return_vector();
1150+
var values = [];
1151+
for (var value of vec) {
1152+
values.push(value);
1153+
}
1154+
assert.deepEqual([10, 20, 30], values);
1155+
assert.deepEqual([10, 20, 30], Array.from(vec));
1156+
vec.delete();
1157+
});
1158+
1159+
test("custom class is iterable", function() {
1160+
if (typeof Symbol === "undefined" || !Symbol.iterator) {
1161+
return;
1162+
}
1163+
var iterable = new cm.CustomIterable();
1164+
var values = [];
1165+
for (var value of iterable) {
1166+
values.push(value);
1167+
}
1168+
assert.deepEqual([1, 2, 3], values);
1169+
assert.deepEqual([1, 2, 3], Array.from(iterable));
1170+
iterable.delete();
1171+
});
11441172
});
11451173

11461174
BaseFixture.extend("map", function() {

test/embind/embind_test.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1292,6 +1292,22 @@ std::vector<SmallClass*> emval_test_return_vector_pointers() {
12921292
return vec;
12931293
}
12941294

1295+
class CustomIterable {
1296+
public:
1297+
CustomIterable() : values_({1, 2, 3}) {}
1298+
1299+
unsigned int count() const {
1300+
return values_.size();
1301+
}
1302+
1303+
int at(unsigned int index) const {
1304+
return values_[index];
1305+
}
1306+
1307+
private:
1308+
std::vector<int> values_;
1309+
};
1310+
12951311
void test_string_with_vec(const std::string& p1, std::vector<std::string>& v1) {
12961312
// THIS DOES NOT WORK -- need to get as val and then call vecFromJSArray
12971313
printf("%s\n", p1.c_str());
@@ -1896,6 +1912,12 @@ EMSCRIPTEN_BINDINGS(tests) {
18961912
register_vector<std::vector<int>>("IntegerVectorVector");
18971913
register_vector<SmallClass*>("SmallClassPointerVector");
18981914

1915+
class_<CustomIterable>("CustomIterable")
1916+
.constructor<>()
1917+
.function("count", &CustomIterable::count)
1918+
.function("at", &CustomIterable::at)
1919+
.iterable<int>("count", "at");
1920+
18991921
class_<DummyForPointer>("DummyForPointer");
19001922

19011923
function("mallinfo", &emval_test_mallinfo);

test/other/embind_tsgen.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,16 @@ class Foo {
3939
void process(const Test& input) {}
4040
};
4141

42+
class IterableClass {
43+
public:
44+
IterableClass() : data{1, 2, 3} {}
45+
unsigned int count() const { return 3; }
46+
int at(unsigned int index) const { return data[index]; }
47+
48+
private:
49+
int data[3];
50+
};
51+
4252
Test class_returning_fn() { return Test(); }
4353

4454
std::unique_ptr<Test> class_unique_ptr_returning_fn() {
@@ -217,6 +227,12 @@ EMSCRIPTEN_BINDINGS(Test) {
217227

218228
register_vector<int>("IntVec");
219229

230+
class_<IterableClass>("IterableClass")
231+
.constructor<>()
232+
.function("count", &IterableClass::count)
233+
.function("at", &IterableClass::at)
234+
.iterable<int>("count", "at");
235+
220236
register_map<int, int>("MapIntInt");
221237

222238
class_<Foo>("Foo").function("process", &Foo::process);

test/other/embind_tsgen.d.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,19 @@ export type EmptyEnum = never/* Empty Enumerator */;
4343

4444
export type ValArrIx = [ Bar, Bar, Bar, Bar ];
4545

46-
export interface IntVec extends ClassHandle {
46+
export interface IntVec extends ClassHandle, Iterable<number> {
4747
push_back(_0: number): void;
4848
resize(_0: number, _1: number): void;
4949
size(): number;
5050
get(_0: number): number | undefined;
5151
set(_0: number, _1: number): boolean;
5252
}
5353

54+
export interface IterableClass extends ClassHandle, Iterable<number> {
55+
count(): number;
56+
at(_0: number): number;
57+
}
58+
5459
export interface MapIntInt extends ClassHandle {
5560
keys(): IntVec;
5661
get(_0: number): number | undefined;
@@ -121,6 +126,9 @@ interface EmbindModule {
121126
IntVec: {
122127
new(): IntVec;
123128
};
129+
IterableClass: {
130+
new(): IterableClass;
131+
};
124132
MapIntInt: {
125133
new(): MapIntInt;
126134
};

test/other/embind_tsgen_ignore_1.d.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,19 @@ export type EmptyEnum = never/* Empty Enumerator */;
5454

5555
export type ValArrIx = [ Bar, Bar, Bar, Bar ];
5656

57-
export interface IntVec extends ClassHandle {
57+
export interface IntVec extends ClassHandle, Iterable<number> {
5858
push_back(_0: number): void;
5959
resize(_0: number, _1: number): void;
6060
size(): number;
6161
get(_0: number): number | undefined;
6262
set(_0: number, _1: number): boolean;
6363
}
6464

65+
export interface IterableClass extends ClassHandle, Iterable<number> {
66+
count(): number;
67+
at(_0: number): number;
68+
}
69+
6570
export interface MapIntInt extends ClassHandle {
6671
keys(): IntVec;
6772
get(_0: number): number | undefined;
@@ -132,6 +137,9 @@ interface EmbindModule {
132137
IntVec: {
133138
new(): IntVec;
134139
};
140+
IterableClass: {
141+
new(): IterableClass;
142+
};
135143
MapIntInt: {
136144
new(): MapIntInt;
137145
};

test/other/embind_tsgen_ignore_2.d.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,19 @@ export type EmptyEnum = never/* Empty Enumerator */;
4242

4343
export type ValArrIx = [ Bar, Bar, Bar, Bar ];
4444

45-
export interface IntVec extends ClassHandle {
45+
export interface IntVec extends ClassHandle, Iterable<number> {
4646
push_back(_0: number): void;
4747
resize(_0: number, _1: number): void;
4848
size(): number;
4949
get(_0: number): number | undefined;
5050
set(_0: number, _1: number): boolean;
5151
}
5252

53+
export interface IterableClass extends ClassHandle, Iterable<number> {
54+
count(): number;
55+
at(_0: number): number;
56+
}
57+
5358
export interface MapIntInt extends ClassHandle {
5459
keys(): IntVec;
5560
get(_0: number): number | undefined;
@@ -120,6 +125,9 @@ interface EmbindModule {
120125
IntVec: {
121126
new(): IntVec;
122127
};
128+
IterableClass: {
129+
new(): IterableClass;
130+
};
123131
MapIntInt: {
124132
new(): MapIntInt;
125133
};

test/other/embind_tsgen_ignore_3.d.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,19 @@ export type EmptyEnum = never/* Empty Enumerator */;
4343

4444
export type ValArrIx = [ Bar, Bar, Bar, Bar ];
4545

46-
export interface IntVec extends ClassHandle {
46+
export interface IntVec extends ClassHandle, Iterable<number> {
4747
push_back(_0: number): void;
4848
resize(_0: number, _1: number): void;
4949
size(): number;
5050
get(_0: number): number | undefined;
5151
set(_0: number, _1: number): boolean;
5252
}
5353

54+
export interface IterableClass extends ClassHandle, Iterable<number> {
55+
count(): number;
56+
at(_0: number): number;
57+
}
58+
5459
export interface MapIntInt extends ClassHandle {
5560
keys(): IntVec;
5661
get(_0: number): number | undefined;
@@ -121,6 +126,9 @@ interface EmbindModule {
121126
IntVec: {
122127
new(): IntVec;
123128
};
129+
IterableClass: {
130+
new(): IterableClass;
131+
};
124132
MapIntInt: {
125133
new(): MapIntInt;
126134
};

0 commit comments

Comments
 (0)