An example of an emscripten module compiled with a RESTProcess descriptor to create a callable Javascript object, with callable methods is as follows. Note the trick is to create the object as a function, then add the methods to it later. This is kind of the opposite of other programming environment, where an object has a special callable annotation added after creation.
function attachMethods(impl,object, prefix, methods) {
for (let i=0; i<methods.size(); ++i) {
let fullMethod=methods.get(i);
if (fullMethod.length>prefix.length && fullMethod.startsWith(prefix)) {
let m=fullMethod.slice(prefix.length).replace(/@/g,'$');
if (m.indexOf('.')<0) {
object[m]= (...args)=>{
let arg=JSON5.stringify(args);
let resultObj=impl.call(fullMethod,arg);
let result=JSON5.parse(resultObj.json());
if (typeof result==="object") {
let r= ()=>{return result;};
attachMethods(resultObj,r,"",resultObj.list());
return r;
}
return result;
};
attachMethods(impl,object[m],fullMethod+'.',methods);
}
}
}
}
// get the emscripten object
let ximpl = new Module.Foo();
//Define the object as a function object, and convert all arguments to JSON.
let x = ()=>{ximpl.call("",JSON.stringify(arguments));};
x.impl=ximpl; // stash a reference to the implementation
attachMethods(xmpl,x,"",ximpl.list());
And the C++ code that implements call and list is as
follows:
struct CppWrapper
{
RPPtr wrappedObject=make_shared<classdesc::RESTProcessVoid>();
CppWrapper()=default;
template <class T> CppWrapper(const T& x): wrappedObject(x) {}
/// call method of this on registry
CppWrapper call(const std::string& method, const string& args) {
json_pack_t jin;
read(args,jin);
return wrappedObject->process("."+method, jin);
}
std::vector<string> list() const {
std::vector<string> methods;
for (auto& i: wrappedObject->list())
{
auto n=i.first.find('.');
if (n==string::npos) continue;
methods.emplace_back(i.first.substr(n+1));
}
return methods;
}
};
struct JSFoo: public CppWrapper
{
JSFoo(): CppWrapper(make_shared<RESTProcessValueObject<Foo>>()) {}
};
EMSCRIPTEN_BINDINGS(Ravel) {
class_<CppWrapper>("CppWrapper")
.function("call",&CppWrapper::call)
.function("list",&CppWrapper::list)
;
class_<JSFoo,base<CppWrapper>>("Foo")
.constructor<>()
;
// for the return value of list()
register_vector<std::string>("vector<string>");
}
At some point, this will be abstracted into something that can live in the Classdesc codebase.