I am trying to embed V8 with a custom class. It is made to be extended and used in JS.
When accessing this in a method other than the constructor, it is null.  It should be able to persist data for that instance of the class.
Am I missing something?
JavaScript
import { Behavior } from '@giz/ecs';
class A extends Behavior {
  constructor(a) {
    super(a);
    this.a = 10;
    print(this); // this works
  }
  update() {
    print(this); // null
  }
}
A;
How I am creating Behavior
    Local<Module> ecsModule = Module::CreateSyntheticModule(
        isolate,
        String::NewFromUtf8(isolate, "@giz/ecs").ToLocalChecked(),
        {String::NewFromUtf8(isolate, "Behavior").ToLocalChecked()},
        [](Local<Context> context, Local<Module> module) -> MaybeLocal<Value>
        {
            auto isolate = context->GetIsolate();
            // behavior constructor right now is an empty function
            auto behavior = Function::New(context, behaviorConstructor).ToLocalChecked();
            module->SetSyntheticModuleExport(
                String::NewFromUtf8(isolate, "Behavior").ToLocalChecked(),
                behavior);
            return MaybeLocal<Value>(True(isolate));
        });
    ecsModule->InstantiateModule(context, ScriptingSystem::moduleResolutionCallback);
    // store the module for later use
    Global<Module> *globalModule = new Global<Module>();
    globalModule->Reset(isolate, ecsModule);
    modules["@giz/ecs"] = globalModule;
Instantiating the class and calling the update function
    v8::HandleScope handle_scope(isolate);
    // globalContext is a v8::Global<v8::Context> with the print function created
    v8::Local<Context> context = Local<Context>::New(isolate, globalContext);
    Context::Scope contextScope(context);
    // ...
    // create script origin, script source, instantiate module, ..
    // ...
    Local<Value> returnValue = module->Evaluate(context).ToLocalChecked();
    Local<Object> returnedBehavior = returnValue->ToObject(context).ToLocalChecked();
    Local<Value> arguments[1];
    arguments[0] = wrapEntity(*behavior->entity);
    // creates an instance of the behavior
    auto instance = returnedBehavior->CallAsConstructor(context, 1, arguments)
                        .ToLocalChecked()
                        ->ToObject(context)
                        .ToLocalChecked();
    // calls the update function
    instance->Get(context, String::NewFromUtf8(isolate, "update").ToLocalChecked())
        .ToLocalChecked()
        ->ToObject(context)
        .ToLocalChecked()
        ->CallAsFunction(context, Null(isolate), 0, nullptr);
Thanks.
                        
The problem is in the last line:
The second argument to
CallAsFunctionis the "receiver", which is the thing that will be accessible asthisinside the called function: since you passNull(isolate)there,thiswill benull. Try passinginstanceinstead.(Essentially, your current code is doing the equivalent of
let instance = new A(); instance.update.call(null);.)Side note: please be aware that
ToLocalCheckedwill crash your process if the previous operation threw an exception. The requirement to convertMaybeLocals toLocals explicitly has been introduced for the purpose of pointing out all the places where C++ code should check for exceptions, and handle them somehow. Only in the rare cases where you can guarantee that an operation won't throw (such as allocating a short string), usingToLocalCheckedis safe. Examples that will currently crash your process:A's constructor throwsA's constructor returnsnullA's constructor installs a getter forupdatethat throwsA's constructor installs a getter forupdatethat returnsnullA common pattern is:
(Yes, working with JavaScript from C++ is difficult.)