mmomtchev / swig

This is SWIG JavaScript Evolution, a fork of the SWIG project with modern JavaScript/TypeScript support including WASM and async
http://www.swig.org
Other
11 stars 1 forks source link

%typemap(directorin) seems not supported in JSE #22

Open Foolyou opened 7 months ago

Foolyou commented 7 months ago

First of all, thanks for your wonderful work on SWIG JSE, it really helps me a lot.

// hello.swg
%module(directors="1") hello_swig;

%feature(director) SomeStruct;
%typemap(directorin) int {
    "This code should be output into hello_wrap.cxx";
}

%inline %{
    struct SomeStruct {
        virtual int doSomething(int someValue);
        virtual ~SomeStruct() = default;
    };
%}

When transforming into javascript-napi with swig -c++ -javascript -napi hello.swg, the output hello_wrap.cxx does not contain the string "This code should be output into hello_wrap.cxx".

While transforming into python with swig -c++ -python hello.swg, the output hello_wrap.cxx contains the string "This code should be output into hello_wrap.cxx".

It is a very common use case in C++ -> JavaScript bindings, as C++ libs often needs a callback function from JavaScript.

Would the author consider of implmeneting this feature in JSE?

mmomtchev commented 7 months ago

Ineed, directors are mostly non-function at the moment - for any of the JS backends.

I am still hesitating to the right approach - should I try to implement the directors - or should I try a more direct approach supporting function-type arguments with inversed typemaps. Generally, SWIG implements the least common denominator for all languages and directors allow to have some callback support that works across all languages. JavaScript in particular can have a much better support. I would like to have a little bit more feedback on real-world use before I decide on an implementation. This is a very significant part that is the only currently missing major SWIG feature.

Foolyou commented 7 months ago

FYI, my current use case is to set JavaScript callbacks into a C++ maps library. The core map functionality is implemented in C++, and it is a platform-neutral lib running on Android/iOS/Desktop etc. When it needs to download styles and tiles, or needs to draw some text, the JavaScript callback is called.

Foolyou commented 7 months ago

JavaScript in particular can have a much better support

That is surely a good idea. But in my view, most of swig users are just porting C/C++ libs to JavaScript(or any other language) but themself not necessarily be daily JavaScript developer. And when people porting a existing lib to JavaScript, they are likely to reference to other language ports of the very lib, and those ports are likely using directors.

mmomtchev commented 7 months ago

I have added a synchronous callback example in https://github.com/mmomtchev/swig-napi-example-project/pull/21 without the use of directors

I am sorry, but currently I cannot afford to invest the necessary time to implement directors in SWIG JSE. This is a very substantial development that will probably take me at least several weeks, probably even up to a month. SWIG JSE does not have any funding, and I am currently living on social welfare in France because of a very significant judiciary scandal with the involvement of the French state and a number of very large international IT companies. I have no other choice but to go from project to project, trying to raise awareness of this situation - which means that the time I spent on each project is limited.

Foolyou commented 7 months ago

Currently I'm using a similar way to implement callbacks, thank you. Respect.

I have read the source code about python's director support, and tried to port it to JSE, but I found this is out of my ability for now.

Foolyou commented 7 months ago

My way is something like this:

%typemap(in) Napi::FunctionReference* (Napi::FunctionReference* ref) {
  ref = new Napi::FunctionReference();
  *ref = Napi::Persistent($input.As<Napi::Function>());
  ref->SuppressDestruct();
  $1 = ref;
}

%typemap(out) Napi::FunctionReference* {
  $result = (*$1).Value();
}

and then you can pass a js function to a C++ struct property or function parameter. But the downside of this method is you need to hand write your ts declarations, unlike your example which keeps type information.

mmomtchev commented 7 months ago

By doing this, your C++ function will receive a a reference to a JS function, not a C++ one. It will need to call it through the JS engine. Besides, you are not freeing ref - you will accumulate references with each call.

Foolyou commented 7 months ago

By doing this, your C++ function will receive a a reference to a JS function, not a C++ one. It will need to call it through the JS engine.

Exactly, also a downside 😁

Besides, you are not freeing ref - you will accumulate references with each call.

This is not a problem in my case, the js callback is saved and periodically called by C++ code, and the function reference will be Unrefed in that object's destructor. If this callback is called asynchronously only once, it could be Unrefed when the C++ code calling the js callback is done. If this callback is called synchronously, the caller should also take care of the Unref of the function reference.

mmomtchev commented 7 months ago

To create a C++ function, you can use a lambda if your C/C++ code will take an std::function reference. If you need an old C-style function pointer, you will have to manage this yourself - there are various very complex solutions that all come down to having a pool of function pointers that you load with the current context.

If you want to pass to use a JS FunctionReference, do not call SuppressDestruct - this should be used only for global variables in the data segment. Use Ref and Unref if you need to handle manually the reference counting. However most of the time this is not needed. You can simply delete your reference and the FunctionReference destructor will handle the counting.

Foolyou commented 7 months ago

Thank you very much, that really helps a lot.