B4J Question RunFunctionWithResult example

LucaMs

Expert
Licensed User
Longtime User
I think I've figured out that it is obviously not possible to avoid making play ping-pong the client and the server, in the case of my game mutilplayer etc.

I would like to save some "sets" of this pingpong game using RunFunctionWithResult.

Can someone give me an example?

For example:
The server asks for an information to the client (a Name)
Dim futName As Future = ws.RunFunctionWithResult("FromS_ReqName", Array As Object (Msg))

How to create the function on the Android device?


Many thanks
 

Erel

B4X founder
Staff member
Licensed User
Longtime User
This method is currently not implemented in the Android client handler.

Change ws_TextMessage to:
B4X:
Private Sub ws_TextMessage(msg As String)
   Try
     Dim jp As JSONParser
     jp.Initialize(msg)
     Dim m As Map = jp.NextObject
     Dim etype As String = m.get("etype")
     Dim params As List = m.get("value")
     Dim event As String = m.get("prop")
     If etype = "runFunction" Then
       CallSub2(CallBack, EventName & "_" & event, params)
     Else If etype = "runFunctionWithResult" Then
       Dim data As Map = CallSub2(CallBack, EventName & "_" & event, params)
       Dim jg As JSONGenerator
       jg.Initialize(CreateMap("type": "data", "data": data))
       ws.SendText(jg.ToString)
     End If
   Catch
     Log("TextMessage Error: " & LastException)
   End Try
End Sub
Your target sub should return a map. Note that I haven't tested it.
 
Upvote 0

LucaMs

Expert
Licensed User
Longtime User
CreateMap is a B4j function. I changed the code (I have not tested it yet)
B4X:
Private Sub ws_TextMessage(msg As String)
  Try
    Dim jp As JSONParser
    jp.Initialize(msg)
    Dim m As Map = jp.NextObject
    Dim etype As String = m.get("etype")
    Dim params As List = m.get("value")
    Dim event As String = m.get("prop")
    If etype = "runFunction" Then
      CallSub2(CallBack, EventName & "_" & event, params)
    Else If etype = "runFunctionWithResult" Then
      Dim data As Map = CallSub2(CallBack, EventName & "_" & event, params)
      Dim jg As JSONGenerator
      Dim m2 As Map : m2.Initialize
      m2.Put("type", "data")
      m2.Put("data", data)
      jg.Initialize(m2)
      ws.SendText(jg.ToString)
    End If
  Catch
    Log("TextMessage Error: " & LastException)
  End Try
End Sub
 
Upvote 0

LucaMs

Expert
Licensed User
Longtime User
java.util.concurrent.Future is not related in any way with this.

Have you tried to implement it as I wrote, with a sub that returns a map?


Not exactly as you have written, for two reasons:
the CreateMap and the fact that I call a routine inside the handler class; but I tried to return a Map from the called routine and then... when I had to write a server-side call:

Dim MyVar As ? = ws.RunFunctionWithResult("FromS_ReqName", Array As Object (Msg))

I Tried "As Map" but I rightly got a "Type Mismatch" since RunFunctionWithResult returns a Future.
 
Upvote 0

LucaMs

Expert
Licensed User
Longtime User
I have not made another attempt (it's better that I do not explain why :)).

However, it is obvious that it would be very useful also in the opposite direction, the client can call a function on the server and get a result, but the websocket client has only SendText.

Can I hope for? :D
 
Upvote 0

LucaMs

Expert
Licensed User
Longtime User
RunFunctionWithResult works, but I get:

java.lang.RuntimeException: java.util.concurrent.TimeoutException

after about 8 seconds only (user/device input).



P.S.
I have "bypassed" the problem by putting the call (RunFunctionWithResult) within a "try-catch" in turn within a "do while" and using a comparison between ticks.

I'll have to use this skeleton (snippet, I'd say) unless there is a way to set this timeout.
 
Last edited:
Upvote 0

LucaMs

Expert
Licensed User
Longtime User
You shouldn't use this method if it waits for the user to do something. Use RunFunction instead.

As you know, I would like to use RunFunctionWithResult to save the code that returns the result, that is another SendEventToServer on the client side and the relative reception server side.

Given your suggestion, there is a reason; perhaps the server's resources?

However, I have received a good surprise that the device receives the "timeout" call (runfunction) and displays a toast even if at that time there is an InputDialog displayed (even if I changed because a toast is not suitable)
 
Upvote 0

Erel

B4X founder
Staff member
Licensed User
Longtime User
You are only making the solution more complicated. RunFunctionWithResult can be useful if you want to run a method that immediately returns a value. Otherwise you are blocking the connection thread and all kinds of bad things can happen.

In this case it will be simpler to just send a message from the server to the device and then send a new message when you are ready from the device to the server.
 
Upvote 0

LucaMs

Expert
Licensed User
Longtime User
You are only making the solution more complicated. RunFunctionWithResult can be useful if you want to run a method that immediately returns a value. Otherwise you are blocking the connection thread and all kinds of bad things can happen.

In this case it will be simpler to just send a message from the server to the device and then send a new message when you are ready from the device to the server.


You are right but this mean that there are no cases where use RunFunctionWithResult.

In this case, the server must wait for the user to choose a nickname.
The server sends the request, the app opens an inputdialog but at that point the user could go half an hour in the bathroom :D

And this is valid for every request form server to client
 
Upvote 0

picenainformatica

Active Member
Licensed User
Longtime User
you can solve giving a session id and a time stamp as a coockie or an hidden field, when user came back from bath can send form (even without web page) and complete operation. Time stamp can used for a timeout.
 
Upvote 0

LucaMs

Expert
Licensed User
Longtime User
Thanks.

I'm using the websockets, then there is a session.

RunFunctionWithResult is performed by the server and it requires a result of the Client.

If I use just RunFunction, I will have to use:

a) Server "Runs a Funcion" (on client) like "Choose_a_Nickname"
b) on client the routine Choose_a_Nickname runs a routine in an activity to ask for a nick
c) this last routine runs a routine on server sending the nick
d) this last routine can be used by server to get the nick.

One request, four routines!

Using RunFunctionWithResult I can save a routine.

The only problem is that the timeout for RunFunctionWithResult is too short (about 8 seconds) and, in this case, the user does not have the time to respond.

I have worked around the problem in this convoluted way.

(Server side)
B4X:
Private Sub ToC_ReqNick(Msg As String) As String
    Private mapResult As Map
    Private ResultOK, TimeOut As Boolean, StartTime As Long
   
    Private Nick As String

    StartTime = DateTime.Now
    Do Until ResultOK OR TimeOut
        Try
            Private fut As Future = ws.RunFunctionWithResult("FromS_ReqNick", Array As Object(Msg))
            ws.Flush
            mapResult = fut.Value
            ResultOK = True
        Catch
            If DateTime.Now - StartTime >= 30000 Then
                TimeOut = True
            End If
        End Try
    Loop
   
    If TimeOut Then
        Return "#Timeout#"
    End If
   
    Nick = mapResult.Get("Nick")
    Return Nick
End Sub
 
Upvote 0

LucaMs

Expert
Licensed User
Longtime User
Could I use this routine in a module:
B4X:
Public Sub OnCLient_Req(WS As WebSocket, ClientFunctionName As String, TimeOutMs As Long, Msg As String) As Map
    Private mapResult As Map : mapResult.Initialize
    Private ResultOK, TimeOut As Boolean, StartTime As Long

    StartTime = DateTime.Now
    Do Until ResultOK OR TimeOut
        Try
            Private fut As Future = WS.RunFunctionWithResult(ClientFunctionName, Array As Object(Msg))
            WS.Flush
            mapResult = fut.Value
            ResultOK = True
        Catch
            If DateTime.Now - StartTime >= TimeOutMs Then
                TimeOut = True
            End If
        End Try
    Loop

    If TimeOut Then
        mapResult.Put("Timeout", True)
    End If

    Return mapResult
End Sub


and call it from a Websocket handler this way:
B4X:
Private m as Map = modWSUtils.OnCLient_Req(WS,"FromS_ReqNick", 30000, "Choose a Nickname")
If m.ContainsKey("Nick") Then
Else
End If

"OnCLient_Req" would be executed in the calling (WebSocket handler) thread, right?
 
Last edited:
Upvote 0
Top