How to create value_object binding with optional fields?
consider the following example:
struct Rectangle {
int x = 0;
int y = 0;
int width = 100;
int height = 100;
int color = 0xFF000000;
};
class Shape {
public:
Shape(Rectangle rect);
};
EMSCRIPTEN_BINDINGS(shapes) {
value_object<Rectangle>("Rectangle")
.field("x", &Rectangle::x)
.field("y", &Rectangle::y)
.field("width", &Rectangle::width)
.field("height", &Rectangle::height)
.field("color", &Rectangle::color);
class_<Shape>("Shape")
.constructor<Rectangle>();
}
Then works flawlessly when I do this in Javascript:
var rect = new Module.Shape( {x: 50, y: 60, width:250, height:10, color: 0xFF000000} )
However, if I just want to only provide the fields I want to override and leave the other fields to their default value like so:
var rect = new Module.Shape( {x: 50, y: 60, width:250, height:10} )
then I get the following error:
Uncaught TypeError: Missing field
How do we make optional fields with embind's value_object? Thanks!
I think I figured out a quick work around: I manually modified emsdk-portable/emscripten/1.37.33/src/embind/embind.js
from:
'toWireType': function(destructors, o) {
// todo: Here we have an opportunity for -O3 level "unsafe" optimizations:
// assume all fields are present without checking.
for (var fieldName in fields) {
if (!(fieldName in o)) {
throw new TypeError('Missing field');
}
}
var ptr = rawConstructor();
for (fieldName in fields) {
fields[fieldName].write(ptr, o[fieldName]);
}
if (destructors !== null) {
destructors.push(rawDestructor, ptr);
}
return ptr;
},
to
'toWireType': function(destructors, o) {
var ptr = rawConstructor();
for (fieldName in fields) {
if (fieldName in o) {
fields[fieldName].write(ptr, o[fieldName]);
}
}
if (destructors !== null) {
destructors.push(rawDestructor, ptr);
}
return ptr;
},
and I think it works now.
You can use emscripten::val && hasOwnProperty(width)
===
You can use emscripten::val && hasOwnProperty to create overloaded constructors.
We can call native constructor based on properties && typeof:
.constructor<emscripten::val>( select_overload<ClassNameHere(emscripten::val)>([](emscripten::val c) {
std::cout << "int constructor wrapped with emscripten::val" << std::endl;
if (c.typeof().as<std::string>() == "string") {
return ClassNameHere("1");
} else if (c.typeof().as<std::string>() == "boolean") {
return ClassNameHere(true);
}
return ClassNameHere(1);
}))
typeof(emscripten::val) === string -> call string contructor typeof(emscripten::val) === boolean -> call bool contructor e.t.c.
template<typename T>
class TemplatedConstructor {
private:
T value;
int p1;
int p2;
std::string str;
public:
TemplatedConstructor() {};
TemplatedConstructor(T val) : value(val) {};
TemplatedConstructor(std::string val) : str(val) {
//std::cout << "TemplatedConstructor(std::string val) called" << std::endl;
};
TemplatedConstructor(int a1, int a2) { p1 = a1; p2 = a2; };
void set(T val) {
value = val;
str = std::to_string(value);
}
T get() {
std::cout << "str:" << str << std::endl;
std::cout << "p1:" << p1 << std::endl;
std::cout << "p2:" << p2 << std::endl;
return value;
}
};
//......
EMSCRIPTEN_BINDINGS(my_module) {
class_<TemplatedConstructor<int>>("TemplatedConstructor")
.constructor<>()
.constructor<int>()
// cant use
//.constructor<std::string>()
// cant use
//.constructor<std::string>(select_overload<void(std::string)>(&TemplatedConstructor<std::string>::TemplatedConstructor<std::string>))
.constructor<int, int>()
// change it to accept one argument
// Idea: use emscripten::val && hasOwnProperty to create overloaded constructors. We can call native constructor based on properties
.constructor<std::string>( select_overload<TemplatedConstructor<int>(std::string, std::string, emscripten::val)>([](std::string a, std::string b, emscripten::val c) {
std::cout << "wrap constructor in lambda 1" << std::endl;
std::cout << "c.typeof().as<std::string>() is " << c.typeof().as<std::string>() << std::endl;
if (c.hasOwnProperty("isStr")){
std::cout << "hasOwnProperty isStr" << std::endl;
std::cout << "c[\"isStr\"].typeof().as<std::string>() is " << c["isStr"].typeof().as<std::string>() << std::endl;
std::cout << "c[\"value\"].typeof().as<std::string>() is " << c["value"].typeof().as<std::string>() << std::endl;
//emscripten::val strVal = c["isStr"];
//std::string getVal;
std::cout << "strVal: " << c["value"].as<std::string>() << std::endl;
return TemplatedConstructor<int>(c["value"].as<std::string>());
}
if (c.hasOwnProperty("isNumber")){
std::cout << "hasOwnProperty isNumber" << std::endl;
return TemplatedConstructor<int>("wrapped constructor");
}
return TemplatedConstructor<int>("wrapped constructor");
}))
.function("set", &TemplatedConstructor<int>::set)
.function("get", &TemplatedConstructor<int>::get);
}
This issue has been automatically marked as stale because there has been no activity in the past year. It will be closed automatically if no further activity occurs in the next 7 days. Feel free to re-open at any time if this issue is still relevant.
Hello ! I'd love to have the feature mentionned in the first post, without the big workaround suggested below :)
There's a way to do this now with std::optional from the PR #23675.
#include <optional>
using namespace emscripten;
struct Rectangle {
int x = 0;
int y = 0;
int width = 100;
int height = 100;
std::optional<int> color = 0xFF000000;
};
EMSCRIPTEN_BINDINGS(shapes) {
register_optional<int>();
value_object<Rectangle>("Rectangle")
.field("x", &Rectangle::x)
.field("y", &Rectangle::y)
.field("width", &Rectangle::width)
.field("height", &Rectangle::height)
.field("color", &Rectangle::color);
class_<Shape>("Shape")
.constructor<Rectangle>();
}
Though the default value won't work.
Another option if you want a default value is to use a getter/setter with optional, but leave the Rectangle class alone. e.g.
struct Rectangle {
int x = 0;
int y = 0;
int width = 100;
int height = 100;
int color = 0xFF000000;
};
...
.field("color",
+[](const Rectangle& r) -> std::optional<int> {
return r.color;
},
+[](Rectangle& r, std::optional<int> color) {
r.color = color.value_or(42);
}
)