Modding Help SOLVED: Receiving data from RpcPromise

Discussion in 'Starbound Modding' started by ncitizen, Oct 13, 2017.

  1. ncitizen

    ncitizen Void-Bound Voyager

    Hello,

    I am trying to dynamically update text in a Label widget, located on a ScriptPane. Naturally, I don't have access to "storage" table in a pane script, so I am trying to pass this data through promise, returned by world.sendEntityMessage(). However, when I try to display promise.result(), I do only get nils. What am I doing wrong?

    Notes:

    • The object.say in message handler works and displays expected results, so the message is passed to entity
    • The promise.error() also returns nil, but promise.succeeded() is false

    object .lua file:
    Code:
    function init()
      storage.MaxCodeLen = 8
    
      storage.code = storage.code or ""
      storage.screen = storage.screen or ""
      storage.unlocked = false
     
      message.setHandler("add", addChar)
      message.setHandler("enter", enter)
      message.setHandler("erase", delChar)
    end
    
    function update(dt)
      object.setAllOutputNodes(storage.unlocked)
     
      -- Automatically lock after timeout.
      if storage.unlocked == true then
        storage.unlocked = false
      end
    end
    
    function addChar(msgName, isLocal, char)
      if string.len(storage.screen) < storage.MaxCodeLen then
        storage.screen = storage.screen .. char
      end
    
      object.say("Current screen: " .. tostring(storage.screen))
      return tostring(storage.screen)
    end
    
    function enter(msgName, isLocal)
      if storage.code == "" then -- In setup mode.
        object.say("Password set!")
        storage.code = storage.screen
        storage.unlocked = true
      else                       -- In check mode.
        if storage.code == storage.screen then
          object.say("Password check success!")
          storage.unlocked = true
        end
      end
    
      storage.screen = "" -- Reset password on exit.
    end
    
    function erase(msgName, isLocal)
      if storage.screen ~= "" then
        storage.screen = string.sub(storage.screen, 1, storage.screen.len - 1)
      end
    end
    


    interface .lua file:
    Code:
    function init()
    end
    
    function input(char)
      local codeRequest = world.sendEntityMessage(pane.sourceEntity(), "add", tostring(char))
      widget.setText("screen", tostring(codeRequest:result()))
    end
    
    function enter()
      world.sendEntityMessage(pane.sourceEntity(), "enter")
    end
    
    function backspace()
      world.sendEntityMessage(pane.sourceEntity(), "erase")
    end
    
    function num_0()
      input("0")
    end
    
    function num_1()
      input("1")
    end
    
    -- Etc. for all 9 digits...
    
     
  2. GonDragon

    GonDragon Pangalactic Porcupine

    I think the handlers are not setted right. Here it is an example of some handlers I setted for other mod:

    Code:
    message.setHandler("getAttributes", function(_, _)
        return getAttributes()
      end)
      message.setHandler("swapAttribute", function(_, _, attribute)
        swapAttribute(attribute)
      end)
    I Think that the handler pases two extra arguments besides any argument you send, that's why the two _, .

    Edit: Nevermind, I saw that you already have those arguments in your functions. I haven't made things with handlers recently, so I'm not really familiarized with them.

    Edit two: Maybe you should be sure that the message have an answer before requesting it. You could made it with something like

    Code:
    
    function input(char)
      local codeRequest = world.sendEntityMessage(pane.sourceEntity(), "add", tostring(char))
    
      local answered = false
      while not answered do
        if codeRequest:finished() then
          widget.setText("screen", tostring(codeRequest:result()))
          answered = true
        end
      end
    end
    
    
     
    Last edited: Oct 13, 2017
  3. bk3k

    bk3k Oxygen Tank

    Hard to follow code from mobile. Anyhow it is important to understand the messaging is asynchronous. You probably don't get your return right off. That's probably why you get nil. You should have update (or a function called by update) check for results.

    edit:
    Now that I'm home, I can expand that answer.

    Code:
    function input(char)
      local codeRequest = world.sendEntityMessage(pane.sourceEntity(), "add", tostring(char))
      widget.setText("screen", tostring(codeRequest:result()))
    end
    
    The problem here is expecting a result on the same tic. You need to make the request one time. After that, you call codeRequest:finished() to see if a result is ready, and if it is then codeRequest:result() to retrieve the return from the other entity.

    You'll need to save the promise non-locally so that you can access it later. Lets try this instead -
    Code:
    function init()
      storage.promises = {}
    end
    
    function input(char)
      local codeRequest = world.sendEntityMessage(pane.sourceEntity(), "add", tostring(char))
      local p_count = #storage.promises
      storage.promises[p_count + 1] = codeRequest
    end
    
    function update(dt)
      local answer
      for i, promise in ipairs(storage.promises) do
        if promise:finished() then
          answer = promise:result()
          table.remove(storage.promises, i)
        end
      end
    
      if answer then
        widget.setText("screen", tostring(answer))
      end
    end
    
    Granted I haven't tested this so errors could exist - especially considering I just typed it into the browser rather than using a decent editor where I can check for obvious mistakes easier. But that should put you on the right track.

    You'd need to be sure to define a script delta too, in order to use update()
     
    Last edited: Oct 14, 2017
  4. ncitizen

    ncitizen Void-Bound Voyager

    GonDragon, bk3k, thank you for replies. Actually, I thought that asking for result() would force function to wait until result will actually arrive but it seems to not be the case.

    GonDragon, as for your suggestion, I don't think that busy waiting is the best option: don't want to hang lua runtime because of some player with bad connection.

    bk3k, now I don't really understand the scope of "storage" table. It seems to exist for the whole lifetime of an object, but what about the assotiated ScriptPane? Will storage be cleared when pane is closed?

    UPD : The "storage" map is unaccessible in panes. Any other ideas on storing value, so it would be available while panel lives?

    UPD2 : Figured it out, just declaring global variable (i.e. without "local") works.
     
    Last edited: Oct 14, 2017
  5. bk3k

    bk3k Oxygen Tank

    Oh didn't realize storage wasn't pre-defined. I'm in the habit of using it because it is already defined in most contexts. Yeah any global would work. No special reason to keep it in storage really. Just a table like any other, with the exception for many things having storage.state - which is actually saved (as boolean only) between sessions. Locals of course only exist in the context they where created and only for the duration of that context.

    The ScriptPane I'm pretty sure is going to start from scratch each time you call it anyhow. I believe it only "exists" from the moment you call it, to the moment you close it, so you'd better keep your persistent info in the object(or whatever entity might call it).
     

Share This Page