Search code examples
javadiscord

Looping Through a Sorted Map with an Asynchronous Method in it in JDA


so I am fairly new to using JDA, and I was just curious as to why this isn't being sorted properly in the discord embed, and returning some of the values on the leaderboard as the incorrect spots. I know that it can be a bit weird using Async stuff like this in loops, but is there a way to work around it?

 case "leaderboard":
                EmbedBuilder embedBuilder = new EmbedBuilder();
                embedBuilder.setTitle("Leaderboard");
                embedBuilder.setColor(Color.GREEN);

                List<Map.Entry<Long, Long>> sortedEntries = new ArrayList<>(LeaderBoardData.loadYamlFile("leaderboard.yml").entrySet());
                sortedEntries.sort(Map.Entry.comparingByValue(Comparator.reverseOrder()));

                StringBuilder leaderboardMessage = new StringBuilder();
                AtomicInteger rank = new AtomicInteger(0);

                for (Map.Entry<Long, Long> entry : sortedEntries) {
                    long userId = entry.getKey();
                    long score = entry.getValue();


                    TestBot.jda.retrieveUserById(userId).queue(user -> {
                        String userName = user != null ? user.getEffectiveName() : "N/A";
                        leaderboardMessage.append("**").append(rank.getAndIncrement()).append("**. ")
                                .append(userName)
                                .append(": ")
                                .append(score)
                                .append("\n");

                        if (rank.get() == 10) {
                            embedBuilder.setDescription(leaderboardMessage.toString());
                            event.replyEmbeds(embedBuilder.build()).queue();
                        }
                    });
                }
                break;

I have debugged up to the point where I know that the userId long and the score long print in the correct order, and it is supposed to be looping correctly, until the LAMBDA. At the moment, it prints something like this:

  1. user1: 32
  2. user2: 59
  3. user3: 54
  4. user4: 50
  5. user5: 27

When I want it to be like:

  1. user2: 59
  2. user3: 54
  3. user4: 50
  4. user1: 32
  5. user5: 27

Solution

  • Since you're calling queue() inside a loop, the order in which the users are retrieved and processed isn't guaranteed to match the order in your sortedEntries list. This leads to the incorrect order in your leaderboard.

    Instead you could try to replace the queue() with a complete(), which will interrupt the current thread until the user has been retrieved and returned by the method. While this is normally not recommomended, it is a way to bypass the problem. If you want to keep the asynchronous behavior of the entire code you've sent, you could simply run it in a new Thread.

    Overall, you should also make sure to chose the proper caching behavior for your bot. If this method is run more often, you might simply want to chose to cache all of the members of the server. Having such logic as you do inside of asynchronous callbacks should be rather avoided.