Calling scoreboards on difrent boardIDs at once only returns the first

Windows

Simulator 2.40

*This is also true for addScore

TLDR; you can't easily call getScores(A) and getScores(B) without putting them together.

It seems unnecessarily hard but if I am being dumb or there is a good reason please tell me.

Update the scoreboards API doc with more info?

Full explanation:
I am trying to receive the data for two scoreboards: lemansone and spaone

pd.scoreboards.getScores("lemansone", function(code, trackTimeTemp)
      lemansoneScoreboardsRank[1] = trackTimeTemp.scores[1].rank
      lemansoneScoreboardsPlayer[1] = trackTimeTemp.scores[1].player
      lemansoneScoreboardsValue[1] = trackTimeTemp.scores[1].value

      print("lemansone")
      printTable(trackTimeTemp)
      printTable(code)
    end)
      
    pd.scoreboards.getScores("spaone", function(code, trackTimeTemp2)
      spaoneScoreboardsRank[1] = trackTimeTemp2.scores[1].rank
      spaoneScoreboardsPlayer[1] = trackTimeTemp2.scores[1].player
      spaoneScoreboardsValue[1] = trackTimeTemp2.scores[1].value

      print("spaone")
      printTable(trackTimeTemp2)
      printTable(code)
    end)

Only this prints:

lemansone
{
	[lastUpdated] = 762985634,
	[scores] = {
		{
			[player] = PastaMan,
			[rank] = 1,
			[value] = 24,
		},
	},
}
{
	[code] = OK,
}

If I write it as:

pd.scoreboards.getScores("lemansone", function(code, trackTimeTemp)
      lemansoneScoreboardsRank[1] = trackTimeTemp.scores[1].rank
      lemansoneScoreboardsPlayer[1] = trackTimeTemp.scores[1].player
      lemansoneScoreboardsValue[1] = trackTimeTemp.scores[1].value

      print("lemansone")
      printTable(trackTimeTemp)
      printTable(code)
      
        pd.scoreboards.getScores("spaone", function(code, trackTimeTemp2)
          spaoneScoreboardsRank[1] = trackTimeTemp2.scores[1].rank
          spaoneScoreboardsPlayer[1] = trackTimeTemp2.scores[1].player
          spaoneScoreboardsValue[1] = trackTimeTemp2.scores[1].value

          print("spaone")
          printTable(trackTimeTemp2)
          printTable(code)
        end)
    end)

And get:

lemansone
{
	[lastUpdated] = 762985699,
	[scores] = {
		{
			[player] = PastaMan,
			[rank] = 1,
			[value] = 24,
		},
	},
}
{
	[code] = OK,
}
spaone
{
	[lastUpdated] = 762985700,
	[scores] = {
		{
			[player] = PastaMan,
			[rank] = 1,
			[value] = 90,
		},
	},
}
{
	[code] = OK,
}

This makes it impossible to use a for loop.
For instance:

local trackNames = {"lemansone", "spaone", "monzathree", "ovalthree", "albertthree"}
    local scoreboards = {
      lemansone = {rank = {}, player = {}, value = {}},
      spaone = {rank = {}, player = {}, value = {}},
      monzathree = {rank = {}, player = {}, value = {}},
      ovalthree = {rank = {}, player = {}, value = {}},
      albertthree = {rank = {}, player = {}, value = {}}
    }

    for i, trackName in ipairs(trackNames) do
      pd.scoreboards.getScores(trackName, function(code, trackTimeTemp)
        if trackTimeTemp and trackTimeTemp.scores and trackTimeTemp.scores[1] then
          scoreboards[trackName].rank[1] = trackTimeTemp.scores[1].rank
          scoreboards[trackName].player[1] = trackTimeTemp.scores[1].player
          scoreboards[trackName].value[1] = trackTimeTemp.scores[1].value

          print(trackName)
          printTable(trackTimeTemp)
        end
      end)
    end

Only prints:

lemansone
{
	[lastUpdated] = 762985823,
	[scores] = {
		{
			[player] = PastaMan,
			[rank] = 1,
			[value] = 24,
		},
	},
}

Solution: This is the only way to call them all easily. (Works best on sim and poorly on device. Simply writing it out is better on device.)

local trackNames = {"lemansone", "spaone", "monzathree", "ovalthree", "albertthree"}
    local scoreboards = {
      lemansone = {rank = {}, player = {}, value = {}},
      spaone = {rank = {}, player = {}, value = {}},
      monzathree = {rank = {}, player = {}, value = {}},
      ovalthree = {rank = {}, player = {}, value = {}},
      albertthree = {rank = {}, player = {}, value = {}}
    }

    local function getScoresSequentially(index)
      if index <= #trackNames then
        local trackName = trackNames[index]
        pd.scoreboards.getScores(trackName, function(code, trackTimeTemp)
          if trackTimeTemp and trackTimeTemp.scores and trackTimeTemp.scores[1] then
            scoreboards[trackName].rank[1] = trackTimeTemp.scores[1].rank
            scoreboards[trackName].player[1] = trackTimeTemp.scores[1].player
            scoreboards[trackName].value[1] = trackTimeTemp.scores[1].value

            print(trackName)
            printTable(trackTimeTemp)
          end

          -- Call getScoresSequentially for the next track
          getScoresSequentially(index + 1)
        end)
      end
    end

    -- Start the sequence
    getScoresSequentially(1)

I also found this for Fore! Track, but didn't have time to investigate why.

I assumed I was doing something wrong but I had to get the game ready for release so I just ploughed on.

My workaround was to call the boards in a chain, similar to you:

  1. call board A
  2. in board A callback: call board B
  3. in board B callback: call board C
  4. in board C callback: call board D
  5. in board D callback: tidy up

I also found this to get all the results faster than calling them all "at once"!

My code was much less optimal than your sequential approach!

1 Like

This limitation is not present using the C API.

I am loading both characters scoreboards at the same time on the title screen of Kuroobi and it works as expected. Both callbacks are called.

2 Likes

Gad to hear it's not just me. :smile:

1 Like

I know that Luke (Paper Plane) also had this problem as they asked me for help and I shared my approach/code.

Let's see what Panic says about this.

It looks like the problem is that the Lua wrapper stores a reference to the callback function in a global, so when you send two requests in a row the second overwrites the first. I think we originally only had one scoreboard per game and then didn't consider this possibility when we added multiple scoreboards. Though I do see a comment about chaining score list calls so maybe Marc intended it to be used that way but forgot to mention it in the docs.

I'll file this, will either add a table so we can track multiple requests or document the behavior and log a warning if you send simultaneous requests. From what Matt says it sounds like chaining works better anyway, so you could use a helper function to make that easier, something like

local requests = {}

function requestScoreboard(name, callback)
	requests[#requests+1] = { name, callback }

	local function getNextRequest()
		pd.scoreboards.getScores(requests[1][1], function(listname, score)
			local callback = requests[1][2]
			-- could also verify that listname == requests[1][1]
			callback(listname, score)
			table.remove(requests, 1)
			
			if #requests > 1 then getNextRequest() end
		end)
	end
	
	if #requests == 1 then getNextRequest() end
end

That's completely untested code (I don't have a game on Catalog, for one) but hopefully it shows the idea even if it doesn't work right. :sweat_smile:

2 Likes

I found the chain does in the end work better on device, than using a loop. I think that it's fine, it's just messy code.

I think that updating the doc would be a great start. :smile:

3 Likes

Also found this (I have multiple metrics for a run that I’m posting at the same time) and spent some time debugging and setting up a chain. This info would be nice to have in the docs.

Here’s my lua code that I use to submit several scores after a run: scoreboards.lua · GitHub

It uses the push/pull metaphor from git, so I add a few scores and then push them to the server. Similarly, I can request a refresh on a scoreboard (or on all of them) and then proceed to pull the data at once.

if cond1 then
  Scoreboards.addScore("score", score)
end 

if cond2 then
  Scoreboards.addScore("longestcombo", longestCombo)
end

Scoreboards.pushScores()