ftk / quickjspp

QuickJS C++ wrapper
415 stars 85 forks source link

How to trigger method overridden in js from c? #28

Open projectitis opened 3 years ago

projectitis commented 3 years ago

Example:

// C++
class ExampleClass {
public:
    void exampleMethod(){
        log( "exampleMethod base" );
    }
}
module.class_<ExampleClass>( "ExampleClass" )
    .fun<&ExampleClass::exampleMethod>( "exampleMethod" );

// JS
class ExtClass extends ExampleClass {
    exampleMethod() {
        console.log("exampleMethod ext");
        super.exampleMethod();
    }
}

I would like this to happen:

// JS
myClass = new ExtClass();

// C++
myClassOpaque->exampleMethod();
// Should log:
//    exampleMethod ext
//    exampleMethod base

I suspect that inside ExampleClass::exampleMethod (and every method where I want this behaviour) I need to check whether the instance has a JS object associated and then manually call exampleMethod on that JS object?

But I'm hoping there is an easier way?

cykoder commented 3 years ago

I think the only way to achieve this is to replace myClassOpaque->exampleMethod(); with a quickjs call to invoke exampleMethod using myClassOpaque as an opaque object. C++ doesnt (and cant?) know that exampleMethod was overriden with JS code.

projectitis commented 3 years ago

Thanks Sam. It's what I was expecting might be the case.

The object myClass is created in javascript. Do you know how I can get the JSValue for myClass from the C++ side? I'd like to make this friendly for the user, so avoiding messy solutions like the user passing it back from javascript to c++ via a method call.

On the C++ side I would like to do something like:

// C++ (psuedo code for brevity)
void myClass::exampleMethod() {
    JSValue jsObj = JS_getObjectForOpaque( this ); // <-- how to do this?
    if (jsObj && JS_HasProperty( jsObj , "exampleMethod" )) {
        JS_EvalThis( jsObj, "this.exampleMethod();" )
    }
}
cykoder commented 3 years ago

I'm actually struggling with that same problem just for objects that have been instantiated in JS. EG:

class MyThing extends Thing {
  constructor() {
    super();
  }

  onUpdate() {
     // blah
  }
}

In C++, MyThing::doSomething wants to call the onUpdate method:

  qjs::Value thisVal = getContext()->newValue(this); // i think this is wrong, need to get existing value from opaque
  JSAtom propAtom = JS_NewAtom(ctx, "onUpdate");
  if (JS_HasProperty(ctx, thisVal.v , propAtom )) {
    JSValue funcVal = JS_GetProperty(ctx, thisVal.v, propAtom);
    const char* str = JS_ToCString(ctx, funcVal);
    if (str) {
      LOG_INFO << str;
    }
    JS_FreeCString(ctx, str);

    JS_Call(ctx, funcVal, thisVal.v, 0, NULL);
  } else {
    // doesnt seem to be set, this is what keeps happening
  }

the not set codepath is being taken unless the onUpdate method was defined in c++ too. likely a wrong thisVal - but also not sure how to get jsvalue from my c++ instance. @ftk any ideas?

Previously with dukglue I was able to do dukglue_pcall_method<void>(getContext(), this, "onUpdate", (double)delta); quite easily. Would be great if quickjshpp supported this. If not a pure quickjs way would be appreciated

ftk commented 2 years ago

Currently testing this feature in sharedptr branch https://github.com/ftk/quickjspp/commit/fa6c6642a60fc04fed4d7e707268abeebb954b07