Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions site/source/docs/api_reference/bind.h.rst
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,38 @@ Classes
:param typename... Policies: |policies-argument|
:returns: |class_-function-returns|

.. cpp:function:: const class_& iterable() const

.. code-block:: cpp

// prototype
template<typename ElementType>
EMSCRIPTEN_ALWAYS_INLINE const class_& iterable(const char* sizeMethodName, const char* getMethodName) const

Makes a bound class iterable in JavaScript by installing ``Symbol.iterator``.
This enables use with ``for...of`` loops, ``Array.from()``, and spread syntax.

:tparam ElementType: The type of elements yielded by the iterator.

:param sizeMethodName: Name of the bound method that returns the number of elements.

:param getMethodName: Name of the bound method that retrieves an element by index.

:returns: |class_-function-returns|

.. code-block:: cpp

class_<MyContainer>("MyContainer")
.function("size", &MyContainer::size)
.function("get", &MyContainer::get)
.iterable<int>("size", "get");

.. code-block:: javascript

const container = new Module.MyContainer();
for (const item of container) { /* ... */ }
const arr = Array.from(container);


.. cpp:function:: const class_& property() const

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1237,9 +1237,12 @@ The following JavaScript can be used to interact with the above C++.
// push value into vector
retVector.push_back(12);

// retrieve value from the vector
for (var i = 0; i < retVector.size(); i++) {
console.log("Vector Value: ", retVector.get(i));
// retrieve a value from the vector
console.log("Vector Value at index 0: ", retVector.get(0));

// iterate over vector
for (var value of retVector) {
console.log("Vector Value: ", value);
}

// expand vector size
Expand Down
42 changes: 42 additions & 0 deletions src/lib/libembind.js
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,35 @@ var LibraryEmbind = {
return this.fromWireType({{{ makeGetValue('pointer', '0', '*') }}});
},

$installIndexedIterator: (proto, sizeMethodName, getMethodName) => {
const makeIterator = (size, getValue) => {
const useBigInt = typeof size === 'bigint';
const one = useBigInt ? 1n : 1;
let index = useBigInt ? 0n : 0;
return {
next() {
if (index >= size) {
return { done: true };
}
const current = index;
index += one;
const value = getValue(current);
return { value, done: false };
},
[Symbol.iterator]() {
return this;
},
};
};

if (!proto[Symbol.iterator]) {
proto[Symbol.iterator] = function() {
const size = this[sizeMethodName]();
return makeIterator(size, (i) => this[getMethodName](i));
};
}
},

_embind_register_std_string__deps: [
'$AsciiToString', '$registerType',
'$readPointer', '$throwBindingError',
Expand Down Expand Up @@ -1723,6 +1752,19 @@ var LibraryEmbind = {
);
},

_embind_register_iterable__deps: [
'$whenDependentTypesAreResolved', '$installIndexedIterator', '$AsciiToString',
],
_embind_register_iterable: (rawClassType, rawElementType, sizeMethodName, getMethodName) => {
sizeMethodName = AsciiToString(sizeMethodName);
getMethodName = AsciiToString(getMethodName);
whenDependentTypesAreResolved([], [rawClassType, rawElementType], (types) => {
const classType = types[0];
installIndexedIterator(classType.registeredClass.instancePrototype, sizeMethodName, getMethodName);
return [];
});
},

_embind_register_class_constructor__deps: [
'$heap32VectorToArray', '$embind__requireFunction',
'$whenDependentTypesAreResolved',
Expand Down
19 changes: 17 additions & 2 deletions src/lib/libembind_gen.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ var LibraryEmbind = {
this.constructors = [];
this.base = base;
this.properties = [];
this.iterableElementType = null;
this.destructorType = 'none';
if (base) {
this.destructorType = 'stack';
Expand All @@ -185,11 +186,16 @@ var LibraryEmbind = {

print(nameMap, out) {
out.push(`export interface ${this.name}`);
const extendsParts = [];
if (this.base) {
out.push(` extends ${this.base.name}`);
extendsParts.push(this.base.name);
} else {
out.push(' extends ClassHandle');
extendsParts.push('ClassHandle');
}
if (this.iterableElementType) {
extendsParts.push(`Iterable<${nameMap(this.iterableElementType, true)}>`);
}
out.push(` extends ${extendsParts.join(', ')}`);
out.push(' {\n');
for (const property of this.properties) {
const props = [];
Expand Down Expand Up @@ -652,6 +658,15 @@ var LibraryEmbind = {
);

},
_embind_register_iterable__deps: ['$whenDependentTypesAreResolved'],
_embind_register_iterable: (rawClassType, rawElementType, sizeMethodName, getMethodName) => {
whenDependentTypesAreResolved([], [rawClassType, rawElementType], (types) => {
const classType = types[0];
const elementType = types[1];
classType.iterableElementType = elementType;
return [];
});
},
_embind_register_class_constructor__deps: ['$whenDependentTypesAreResolved', '$createFunctionDefinition'],
_embind_register_class_constructor: function(
rawClassType,
Expand Down
1 change: 1 addition & 0 deletions src/lib/libsigs.js
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ sigs = {
_embind_register_float__sig: 'vppp',
_embind_register_function__sig: 'vpippppii',
_embind_register_integer__sig: 'vpppii',
_embind_register_iterable__sig: 'vpppp',
_embind_register_memory_view__sig: 'vpip',
_embind_register_optional__sig: 'vpp',
_embind_register_smart_ptr__sig: 'vpppipppppppp',
Expand Down
23 changes: 22 additions & 1 deletion system/include/emscripten/bind.h
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,12 @@ void _embind_register_class_class_property(
const char* setterSignature,
GenericFunction setter);

void _embind_register_iterable(
TYPEID classType,
TYPEID elementType,
const char* sizeMethodName,
const char* getMethodName);

EM_VAL _embind_create_inheriting_constructor(
const char* constructorName,
TYPEID wrapperType,
Expand Down Expand Up @@ -1587,6 +1593,19 @@ class class_ {
return *this;
}

template<typename ElementType>
EMSCRIPTEN_ALWAYS_INLINE const class_& iterable(
const char* sizeMethodName,
const char* getMethodName) const {
using namespace internal;
_embind_register_iterable(
TypeID<ClassType>::get(),
TypeID<ElementType>::get(),
sizeMethodName,
getMethodName);
return *this;
}

template<
typename FieldType,
typename... Policies,
Expand Down Expand Up @@ -1847,6 +1866,8 @@ template<typename T, class Allocator=std::allocator<T>>
class_<std::vector<T, Allocator>> register_vector(const char* name) {
typedef std::vector<T, Allocator> VecType;
register_optional<T>();
using VectorElementType =
typename internal::RawPointerTransformer<T, std::is_pointer<T>::value>::type;

return class_<VecType>(name)
.template constructor<>()
Expand All @@ -1855,7 +1876,7 @@ class_<std::vector<T, Allocator>> register_vector(const char* name) {
.function("size", internal::VectorAccess<VecType>::size, allow_raw_pointers())
.function("get", internal::VectorAccess<VecType>::get, allow_raw_pointers())
.function("set", internal::VectorAccess<VecType>::set, allow_raw_pointers())
;
.template iterable<VectorElementType>("size", "get");
}

////////////////////////////////////////////////////////////////////////////////
Expand Down
22 changes: 22 additions & 0 deletions test/embind/embind.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1141,6 +1141,28 @@ module({
small.delete();
vec.delete();
});

test("std::vector is iterable", function() {
var vec = cm.emval_test_return_vector();
var values = [];
for (var value of vec) {
values.push(value);
}
assert.deepEqual([10, 20, 30], values);
assert.deepEqual([10, 20, 30], Array.from(vec));
vec.delete();
});

test("custom class is iterable", function() {
var iterable = new cm.CustomIterable();
var values = [];
for (var value of iterable) {
values.push(value);
}
assert.deepEqual([1, 2, 3], values);
assert.deepEqual([1, 2, 3], Array.from(iterable));
iterable.delete();
});
});

BaseFixture.extend("map", function() {
Expand Down
22 changes: 22 additions & 0 deletions test/embind/embind_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1304,6 +1304,22 @@ std::vector<SmallClass*> emval_test_return_vector_pointers() {
return vec;
}

class CustomIterable {
public:
CustomIterable() : values_({1, 2, 3}) {}

unsigned int count() const {
return values_.size();
}

int at(unsigned int index) const {
return values_[index];
}

private:
std::vector<int> values_;
};

void test_string_with_vec(const std::string& p1, std::vector<std::string>& v1) {
// THIS DOES NOT WORK -- need to get as val and then call vecFromJSArray
printf("%s\n", p1.c_str());
Expand Down Expand Up @@ -1908,6 +1924,12 @@ EMSCRIPTEN_BINDINGS(tests) {
register_vector<std::vector<int>>("IntegerVectorVector");
register_vector<SmallClass*>("SmallClassPointerVector");

class_<CustomIterable>("CustomIterable")
.constructor<>()
.function("count", &CustomIterable::count)
.function("at", &CustomIterable::at)
.iterable<int>("count", "at");

class_<DummyForPointer>("DummyForPointer");

function("mallinfo", &emval_test_mallinfo);
Expand Down
16 changes: 16 additions & 0 deletions test/other/embind_tsgen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ class Foo {
void process(const Test& input) {}
};

class IterableClass {
public:
IterableClass() : data{1, 2, 3} {}
unsigned int count() const { return 3; }
int at(unsigned int index) const { return data[index]; }

private:
int data[3];
};

Test class_returning_fn() { return Test(); }

std::unique_ptr<Test> class_unique_ptr_returning_fn() {
Expand Down Expand Up @@ -234,6 +244,12 @@ EMSCRIPTEN_BINDINGS(Test) {

register_vector<int>("IntVec");

class_<IterableClass>("IterableClass")
.constructor<>()
.function("count", &IterableClass::count)
.function("at", &IterableClass::at)
.iterable<int>("count", "at");

register_map<int, int>("MapIntInt");

class_<Foo>("Foo").function("process", &Foo::process);
Expand Down
10 changes: 9 additions & 1 deletion test/other/embind_tsgen.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,19 @@ export type EmptyEnum = never/* Empty Enumerator */;

export type ValArrIx = [ FirstEnum, FirstEnum, FirstEnum, FirstEnum ];

export interface IntVec extends ClassHandle {
export interface IntVec extends ClassHandle, Iterable<number> {
push_back(_0: number): void;
resize(_0: number, _1: number): void;
size(): number;
get(_0: number): number | undefined;
set(_0: number, _1: number): boolean;
}

export interface IterableClass extends ClassHandle, Iterable<number> {
count(): number;
at(_0: number): number;
}

export interface MapIntInt extends ClassHandle {
keys(): IntVec;
get(_0: number): number | undefined;
Expand Down Expand Up @@ -131,6 +136,9 @@ interface EmbindModule {
IntVec: {
new(): IntVec;
};
IterableClass: {
new(): IterableClass;
};
MapIntInt: {
new(): MapIntInt;
};
Expand Down
10 changes: 9 additions & 1 deletion test/other/embind_tsgen_ignore_1.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,19 @@ export type EmptyEnum = never/* Empty Enumerator */;

export type ValArrIx = [ FirstEnum, FirstEnum, FirstEnum, FirstEnum ];

export interface IntVec extends ClassHandle {
export interface IntVec extends ClassHandle, Iterable<number> {
push_back(_0: number): void;
resize(_0: number, _1: number): void;
size(): number;
get(_0: number): number | undefined;
set(_0: number, _1: number): boolean;
}

export interface IterableClass extends ClassHandle, Iterable<number> {
count(): number;
at(_0: number): number;
}

export interface MapIntInt extends ClassHandle {
keys(): IntVec;
get(_0: number): number | undefined;
Expand Down Expand Up @@ -142,6 +147,9 @@ interface EmbindModule {
IntVec: {
new(): IntVec;
};
IterableClass: {
new(): IterableClass;
};
MapIntInt: {
new(): MapIntInt;
};
Expand Down
Loading