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.