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]);
Related
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.
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 !
I have a Controller with the following method:
public void ExportList()
{
var out = GenExport();
CsvExport<LiveViewListe> csv = new CsvExport<LiveViewListe>(out);
Response.Write(csv.Export());
}
this should generate a csv file which the user can download.
I call this method via a jQuery request in my view:
$.getJSON('../Controller2/ExportList', function (data) {
//...
});
the problem is, that I don't get any download and I don't know why. The method is called but without a download.
What is wrong here?
Your controller methods need to always return an ActionResult. So the method should look more like
public ActionResult ExportList()
{
var export = GenExport();
CsvExport<LiveViewListe> csv = new CsvExport<LiveViewListe>(export);
return new CsvResult(csv);
}
Where CsvResult is a class inheriting from ActionResult and doing the necessary to prompt the user for download of your Csv results.
For example, if you really need to Response.Write this could be:
public class CsvResult : ActionResult
{
private CsvExport<LiveViewListe> data;
public CsvResult (CsvExport<LiveViewListe> data)
{
this.data = data;
}
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
HttpResponseBase response = context.HttpContext.Response;
response.ContentType = "text/csv";
response.AddHeader("Content-Disposition", "attachment; filename=file.csv"));
if (data!= null)
{
response.Write(data.Export());
}
}
}
You could also think about making this more generic, if your CsvExport class has the Export method:
public class CsvResult<T> : ActionResult
{
private CsvExport<T> data;
public CsvResult (CsvExport<T> data)
{
this.data = data;
}
.... same ExecuteResult code
}
Now it supports any of your csv downloads, not just LiveViewListe.
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.
I have some issues developing my own plugin. I want my plugin to run as a background process on an android device. Searching the internet, I found using the Service superclass (or a sub-class) being most relevant. Below is some sample code, the execute method in Hello.java is successfully called - the app crashes when I try to start the service (the constructor is successfully instantiated though). Any hints?
//Myservice.java
public class MyService extends IntentService {
public MyService(String name) {
super(name);
Log.d("asd", "constructor");
}
#Override
public void onHandleIntent(Intent intent) {
Log.d("asd", "something");
}
}
//Hello.java
public class Hello extends CordovaPlugin {
#Override
public boolean execute(String action, JSONArray data, CallbackContext callbackContext) throws JSONException {
Log.d("asd", "we are in execute, hurray!");
Context context = cordova.getActivity().getApplicationContext();
Intent intent = new Intent(context, MyService.class);
MyService service = new MyService("WifiP2pService");
//either this
service.startService(intent);
//or this, tried both
//context.startService(intent);
return true;
}
}
// /plugins/the.package/www/hello.js
module.exports = {
greet: function (name, successCallback, errorCallback) {
cordova.exec(successCallback, errorCallback, "Hello", "greet", [name]);
}
};
// /www/index.js - in onDeviceReady function
var success = function(message) {
alert(message);
}
var failure = function() {
alert("Error calling the plugin");
}
hello.greet("Superdids", success, failure);
I am fairly new to android programming, so any hints would be muchly appreciated!