Passing JS function to C++ function emscripten - javascript

I have function with such signature :
void generate_effect(const std::string& file_name, const std::string& out_dir) {
...
...
}
and binding such as:
EMSCRIPTEN_BINDINGS(main)
{
function("generateEffect", &generate_effect);
}
so in JS i call it in such way:
const object = await Module()
object.generateEffect("file_name", "out_dir");
This works fine
But i need to make something like this:
object.generateEffect("file_name", "out_dir", function callback(success, msg) {
if(success) ...
else ...
});
The trouble is that i've tries different ways passing callback to C++ function, but they did not work for me, i've tried:
void generate_effect(const std::string& file_name, const std::string& out_dir,
emscripten::val callback) // works only when callback has no args
void generate_effect(const std::string& file_name, const std::string& out_dir,
void(*callback)(bool, const char*)) // i must not use raw pointers
I've red about optional_override and i think my bindings should look like this:
function(
"generateEffect",
optional_override(
[](manager& this_, const val& error) {
this_.add_error_listener(
make_error_listener(
[error](const std::string& name, const std::string& message) {
error(name, message);
}));
}))
I have no idea how to pass callback with two params to C++ function in emscripten, i'll be glad for any help !

Related

JSInvokable Blazor method not being called

I'm trying to invoke a Blazor method in JavaScript inside of an OnSuccess callback for the Plaid API.
Here's the JavaScript that's being run:
async function InitializePlaidLink(objRef, linkToken) {
//console.log("linkToken:" + linkToken);
const handler = Plaid.create({
token: linkToken,
onSuccess: (public_token, metadata) => {
//console.log("public_token: ");
//console.log(public_token);
objRef.invokeMethodAsync('OnPlaidLinkSuccess', public_token);
//console.log("After Invoke Method Async")
},
onLoad: () => {},
onExit: (err, metadata) => {},
onEvent: (eventName, metadata) => {},
//required for OAuth; if not using OAuth, set to null or omit:
//receivedRedirectUri: window.location.href,
});
handler.open();
}
Here's the Blazor code being used:
private string LinkToken { get; set; } = string.Empty;
private string PublicToken { get; set; } = string.Empty;
private async Task InitializePlaid()
{
this.LinkToken = await this.apiService.GetPlaidLinkToken();
var dotNetReference = DotNetObjectReference.Create(this);
await this.jsRuntime.InvokeVoidAsync
(
"InitializePlaidLink",
dotNetReference,
this.LinkToken
);
}
[JSInvokable]
public void OnPlaidLinkSuccess(string publicToken)
{
this.PublicToken = publicToken;
}
The Blazor method InitializePlaid is being called to invoke the JS method InitializePlaidLink. Then, on success, the Blazor method OnPlaidLink Success should be called.
I used log statements to confirm that there is a public_token and the JS after the objRef.invokeMethodAsync() is being reached. Also I was able to invoke a Blazor method in a similar way with a different JS method, just not a method with the Plaid API and the onSuccess callback.
The problem is that the OnPlaidLinkSuccess method must be static as follows:
[JSInvokable]
public static void OnPlaidLinkSuccess(string publicToken)
{
this.PublicToken = publicToken;
}
If you have to define a non-static function, then it will be a bit more complicated.
In this case, it is necessary to send a reference of the current component to the JavaScript method. What follows is a solution that you have to adapt yourself with your own codes.
For this reason, I first create this reference using the DotNetObjectReference.Create method and then send it to the JavaScript code using the jSRuntime.InvokeVoidAsync method. In the example below, JsSample is the name of the current component.
Also defined here, onclick refers to a method inside this component.
This reference should also be disposed at the end of the component's work. That's why you see #IDisposable implements.
#page "/js-sample"
#implements IDisposable
#inject IJSRuntime jSRuntime
<button class="btn btn-primary" #onclick="CallInstanceMethod">Invoke Instance Method</button>
#code
{
private DotNetObjectReference<JsSample> objectReference;
[JSInvokable]
public string GetAddress()
{
return "123 Main Street";
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if(firstRender)
{
objectReference = DotNetObjectReference.Create(this);
}
}
private async Task CallInstanceMethod()
{
await jSRuntime.InvokeVoidAsync("JsFunctionHelper.invokeDotnetInstanceFunction", objectReference);
}
public void Dispose()
{
objectReference?.Dispose();
}
}
Now the javascript code that uses this receiving slot will be as follows. In these codes, addressProvider is the received objectReference that can be used to call the component's non-static GetAddress method:
window.JsFunctionHelper = {
invokeDotnetInstanceFunction: function (addressProvider) {
addressProvider.invokeMethodAsync("GetAddress").then((data) => {
console.log(data);
});
}
};
OnPlaidLinkSuccess was being called correctly. The property this.PublicToken was being correctly updated, but the DOM was not being updated for the user to see. Calling this.StateHasChanged() fixed this.

Get the calling object

I am using websocket from node, and I am trying to implement a broadcast method that will send a message to all clients except for the client that sent the message.
To do this, I need to know the
export class WebSocketRoom {
private _clients: WebSocketConnection[] = []
public get clients(): WebSocketConnection[] { return this._clients }
public broadcast(event: string, message: any) {
this.clients.forEach(client => {
client.emit(event, message)
})
return this
}
}
To access the method broadcast, I do this:
class Test extends Module {
public constructor(client: WebSocketConnection) {
super(client)
let room = new WebSocketRoom('hi')
room.broadcast('cool', 'sweet')
}
}
I tried doing a console log of WebSocketRoom.caller but that gives me this error:
TypeError: 'caller' and 'arguments' are restricted function properties and cannot be accessed in this context.
Is there a way that I can access the object that called broadcast from within the broadcast method without passing it as a parameter?
this.clients.forEach(client => {
if(client == sender) return
client.emit(event, message)
})
Side note
In C#, it is done like so:
public static void MyMethod(this GameObject obj, int var1, int var2) {
obj.add(var1, var2)
}
MyMethod(1,2)
As you can see obj is not passed in when it is called.

Pass a callback from Unity to JavaScript and back to Unity

I am trying to run an Action that sends data to the javascript on the browser, and when the browser finishes it runs a callback, and when the callback completes, it runs the item in the C# which runs the original callback.
Here are is the call order: GameSmart.User.IsGuest(Action origCallback) -> IsGuestUser(/*Executes the javascript*/) -> GuestResponse(string json) -> origCallback()
Once I compile and run the game, I get the following message in the chrome console:
MarshalDirectiveException: Cannot marshal type 'GameSmart.Response`1<GameSmart.IsGuestResponse>'.
I am not sure what that is saying or even means. Is there something I am doing wrong?
This is the class GameSmart.User:
public class User : API {
[DllImport("__Internal")]
public static extern void IsGuestUser(Response<IsGuestResponse> response);
[MonoPInvokeCallback(typeof(Action))]
public static void GuestResponse(Response<IsGuestResponse> r, string data) {
r.callback(JsonUtility.FromJson<IsGuestResponse>(data));
}
public static void IsGuest(Action<IsGuestResponse> callback) {
IsGuestUser(new Response<IsGuestResponse>(callback));
}
}
Here are the Response/Respond classes:
public class Respond { }
public class Response<T> : Respond {
public Action<T> callback;
public Response(Action<T> cb) {
callback = cb;
}
public void Action(Action act, params object[] args) {
act();
}
}
The JavaScript portion looks like this:
var GameSmartJs = {
$GameSmartJs: {},
IsGuestUser: function (obj) {
gamesmart.user.isGuest(function (result) {
this.runCallback('GuestResponse', obj, result);
});
},
runCallback: function (callbackName, callback, result) {
GameSmartJs[callbackName] = callback;
Runtime.dynCall('vZ', callback, Pointer_stringify(result));
}
};
autoAddDeps(GameSmartJs, '$GameSmartJs');
mergeInto(LibraryManager.library, GameSmartJs);
Edit
As per #Programmer suggested, to use _malloc and writeStringToMemory I tried this, and it produces the same error message.
IsGuestUser: function (obj) {
gamesmart.user.isGuest(function (result) {
GameSmartUser.GuestResponse = obj;
var buffer = _malloc(lengthBytesUTF8(result) + 1);
writeStringToMemory(result, buffer);
Runtime.dynCall('vZ', obj, buffer);
});
},
For the next soul that comes across this:
Runtime.dynCall('vZ', obj, buffer);
should be changed to
Runtime.dynCall('vi', obj, [buffer]);

How to know if callback function used is same in Cordova?

I am trying to develop a cordova plugin for my android library. Below is a part of MyCordovaPlugin.js file:
var exec = require('cordova/exec');
module.exports = {
doA : function(message, successCallback) {
exec(successCallback,
null, // No failure callback
"MyCordovaPlugin",
"doA",
[message]);
},
doB : function(message, successCallback) {
exec(successCallback,
null, // No failure callback
"MyCordovaPlugin",
"doB",
[message]);
}
};
As mentioned above, I have two methods doA and doB which takes two arguments - message and successCallback.
In my native java code, I need to know if the successCallback used in doA and doB is same or not. How can I do this? How can I map a javascript function so that I can check if it's been used again?
I have callbackContext value in my native java code. But the value would be different when called.
It's not possible to distinguish at the native level one Javascript function from another, since the native layer is just passed a randomly generated ID which it uses to invoke the correct Javascript callback function.
So I would pass through an additional argument to your plugin methods which allows you to distiguish this at the native level.
Something like this:
myPlugin.js
var exec = require('cordova/exec');
module.exports = {
doA : function(message, successCallback, callbackName) {
exec(successCallback,
null, // No failure callback
"MyCordovaPlugin",
"doA",
[message, callbackName]);
},
doB : function(message, successCallback, callbackName) {
exec(successCallback,
null, // No failure callback
"MyCordovaPlugin",
"doB",
[message, callbackName]);
}
};
myApp.js
function foo(){
console.log("foo");
}
function bar(){
console.log("bar");
}
myPlugin.doA("Some message", foo, "foo");
myPlugin.doB("Some message", foo, "foo");
myPlugin.doA("Some message", bar, "bar");
myPlugin.doB("Some message", bar, "bar");
myPlugin.java
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
String message = args.getString(0);
String callbackName = args.getString(1);
// etc
}

Executing named `fat arrow` from .then() function

Success gets called in a successful factory response in the then callback:
This doesn't work, it cannot find response:
this.storeFactory.getSafes().then(success(response), failure());
How to I pass the response to the success function correctly?
var success = (response: any): void => {
scope.safes = response.data.safes;
localStorage.setItem('safeCount', scope.safes.length);
this.$http.get('/app/dashboard/safes/safes.html', { cache: this.$templateCache }).success((tplContent): void => {
element.replaceWith(this.$compile(tplContent)(scope));
});
}
The long hand version works fine, but I feel like it is very messy.
this.storeFactory.getSafes().then((response: any): void => {
scope.safes = response.data.safes;
localStorage.setItem('safeCount', scope.safes.length);
this.$http.get('/app/dashboard/safes/safes.html', { cache: this.$templateCache }).success((tplContent): void => {
element.replaceWith(this.$compile(tplContent)(scope));
});
}
How to I pass the response to the success function correctly?
You don't. You pass the success function to the then method, then the promise will pass the result value to your success function. That's how callbacks work.
All you need to do is declare response as a paramter of your function. You must not call the function yourself - you only should pass it as a callback:
this.storeFactory.getSafes().then(success, failure);
Also you will need to define the functions before you pass them to then. If you only declare them, and pass undefined values to then, they will be ignored. Use
var success = (response: any): void => {
scope.safes = response.data.safes;
localStorage.setItem('safeCount', scope.safes.length);
this.$http.get('/app/dashboard/safes/safes.html', { cache: this.$templateCache }).success((tplContent): void => {
element.replaceWith(this.$compile(tplContent)(scope));
});
};
var failure = (): void => {
this.$http.get('/app/shared/mocks/tableError.html', { cache: this.$templateCache }).success((tplContent): void => {
element.replaceWith(this.$compile(tplContent)(scope));
});
}
this.storeFactory.getSafes().then(success, failure);
However, arrow functions are actually supposed to be defined inline, without assigning them to a specific variable. (You called this the "long hand version" in your question, even if it's actually shorter). Just use that and you won't face these problems.
In general, I would recommend to avoid defining functions in variable assignments completely. If you need a variable, just use a declaration instead (Typescript syntax should not vary much).
I can't speak to the ES2015 syntax or Typescript, however the way you're passing back your success callback looks suspect.
instead of
this.storeFactory.getSafes().then(success(response), failure());
you should use
this.storeFactory.getSafes().then(success, failure);
The callback of your AJAX call also needs to use arrow functions:
this.$http.get('/app/dashboard/safes/safes.html', { cache: this.$templateCache }).success((tplContent) => {
element.replaceWith(this.$compile(tplContent)(scope));
});

Categories