frengor / UltimateAdvancementAPI

A powerful API to create custom advancements for your Minecraft server.
https://modrinth.com/plugin/ultimateadvancementapi
GNU General Public License v3.0
116 stars 13 forks source link

Weird lag spikes when updating advancements #73

Closed NickCloudAT closed 10 months ago

NickCloudAT commented 10 months ago

Hey.. So I noticed my client to have micro stutters everytime an advancements updates / progress changes..

It's very subtle but I'm prone to be annoyed by micro stutters so I noticed it. To be sure I also looked at my F3 menu with debug charts enabled: image

As seen "scheduledExecutables" spikes everytime I get an update for the advancements. Is there any way I can avoid this? I have some advancements that require progress increments pretty much every 5 seconds, or for example when shooting a bow etc.

frengor commented 10 months ago

Hi! Can you please add some code to reproduce the issue?

NickCloudAT commented 10 months ago

Hi! Can you please add some code to reproduce the issue?

Probably a bit tangled but this is the main file where I create the Advancements:

public class WCAdvancementsHandler {

    private AdvancementTab advancementTab;
    private RootAdvancement root;
    private UltimateAdvancementAPI api = null;
    private AdvancementChecks advancementChecks;

    private final float yOffset = 3;

    private final HashMap<String, BaseAdvancement> BASE_ADVANCEMENTS = new HashMap<>();

    public WCAdvancementsHandler(){
        advancementChecks = new AdvancementChecks(this);

        registerAdvancements();
        advancementChecks.startCheckRunnable();
    }

    public void registerAdvancements(){
        if(!Main.getInstance().getServer().getPluginManager().isPluginEnabled("UltimateAdvancementAPI")){
            Main.getInstance().getLogger().log(Level.INFO, "UltimateAdvancementAPI is not installed. Custom Advancements will not work.");
            return;
        }

        api = UltimateAdvancementAPI.getInstance(Main.getInstance());
        advancementTab = api.createAdvancementTab("wintercraft_advancements");

        AdvancementDisplay rootDisplay = new AdvancementDisplay(Material.JACK_O_LANTERN, "§b§lWinterCraft 2024", AdvancementFrameType.TASK, true, true, 0, yOffset, "§eYour WinterCraft 2024 journey starts here!");

        root = new RootAdvancement(advancementTab, "root", rootDisplay, "textures/block/snow.png");

        buildAdvancements();

        advancementTab.registerAdvancements(root, new HashSet<>(BASE_ADVANCEMENTS.values()));

        advancementTab.getEventManager().register(advancementTab, PlayerLoadingCompletedEvent.class, e -> {
            advancementTab.showTab(e.getPlayer());
            advancementTab.grantRootAdvancement(e.getPlayer());
        });

        for(final Player on : Bukkit.getOnlinePlayers()){
            advancementTab.showTab(on);
        }
    }

    public AdvancementTab getWCAdvancementTab(){
        return advancementTab;
    }

    public RootAdvancement getWCRootAdvancement(){
        return root;
    }

    public void grantAdvancementByName(final Player p, final String name){
        if(api == null || !BASE_ADVANCEMENTS.containsKey(name))return;

        BASE_ADVANCEMENTS.get(name).grant(p);
    }

    public boolean hasEarnedAdvancement(final Player p, final String name){
        if(api == null || !BASE_ADVANCEMENTS.containsKey(name))return false;

        return BASE_ADVANCEMENTS.get(name).isGranted(p);
    }

    public void grantAdvancementIfHas(final Player p, final String toGrant, final String ifHas){
        if(api == null || !BASE_ADVANCEMENTS.containsKey(toGrant) || !BASE_ADVANCEMENTS.containsKey(ifHas) || !hasEarnedAdvancement(p, ifHas))return;

        grantAdvancementByName(p, toGrant);
    }

    public BaseAdvancement getAdvancementByName(final String name){
        if(api == null || !BASE_ADVANCEMENTS.containsKey(name))return null;

        return BASE_ADVANCEMENTS.get(name);
    }

    public UltimateAdvancementAPI getAdvancementAPI(){
        return api;
    }

    //
    private void buildAdvancements() {
        final ItemStack slimeLocator = Main.getItemManageHandler().getItemFromFile("slimeLocator");
        final ItemMeta slimeLocatorMeta = slimeLocator.getItemMeta();

        // Define Advancements
        createAdvancement(WCOpenSettingsMenuAdvancement.class, "settings_menu", Material.REDSTONE, "Open the settings menu", AdvancementFrameType.TASK, root, 1F, yOffset, "§cOpen the settings menu with §e/wc settings", 1);

        createAdvancement(WCUseSlimeLocatorAdvancement.class, "use_slimelocator", slimeLocator, "Use a Slime Locator", AdvancementFrameType.TASK, getAdvancementByName("settings_menu"), 2F, yOffset - 1, "§eUse a Slime Locator at least once", 1);

        slimeLocatorMeta.setCustomModelData(1);
        slimeLocator.setItemMeta(slimeLocatorMeta);

        createAdvancement(WCSlimeLocatorFindSlimeChunkAdvancement.class, "find_slimechunk", slimeLocator, "§5Slime chunk ahead!", AdvancementFrameType.CHALLENGE, getAdvancementByName("use_slimelocator"), 3F, yOffset - 1, "§cUse a Slime Locator and find a slime chunk", 1);

        createAdvancement(WCTravelToNetherRoofAdvancement.class, "travel_netherroof", Material.OBSIDIAN, "How did I get here?!", AdvancementFrameType.TASK, getAdvancementByName("settings_menu"), 2F, yOffset + 1, "§eUse a Nether portal to reach the Nether roof", 1);

        createAdvancement(WCBreakNetheriteHoeAdvancement.class, "break_netherite_hoe", Material.NETHERITE_HOE, "§5Serious loss :(", AdvancementFrameType.CHALLENGE, getAdvancementByName("travel_netherroof"), 3F, yOffset + 1, "§cHow did you manage to break a Netherite Hoe?!", 1);

        createAdvancement(WCCraftGanAdvancement.class, "craft_gan", Material.COMMAND_BLOCK, "What is this currency?", AdvancementFrameType.TASK, getAdvancementByName("settings_menu"), 2F, yOffset, "§eCraft some Gan with your precious diamonds.", 1);

        createAdvancement(WCCraftHalAdvancement.class, "craft_hal", Material.CHAIN_COMMAND_BLOCK, "You can even split it :O", AdvancementFrameType.TASK, getAdvancementByName("craft_gan"), 3F, yOffset, "§eDestroy some Gan and make some Hal out of it.", 1);

        createAdvancement(WCCraftQuaAdvancement.class, "craft_qua", Material.REPEATING_COMMAND_BLOCK, "Compressed money!", AdvancementFrameType.TASK, getAdvancementByName("craft_hal"), 4F, yOffset, "§eCompress your Gan and make some Qua.", 1);

        createAdvancement(WCHaveEnoughQuaAdvancement.class, "have_enough_qua", Material.REPEATING_COMMAND_BLOCK, "§5I'm rich, bitch!", AdvancementFrameType.GOAL, getAdvancementByName("craft_qua"), 5F, yOffset, "§cHave a full stack of Qua in your inventory.", 1);

        createAdvancement(WCWalkDistanceAdvancement.class, "walk_100_km", Material.GOLDEN_BOOTS, "§5Walk in the park", AdvancementFrameType.GOAL, getAdvancementByName("settings_menu"), 1F, yOffset + 2, "§cWalk 100 kilometers.", 100000);

        createAdvancement(WCCrouchDistanceAdvancement.class, "crouch_25_km", Material.GOLDEN_LEGGINGS, "§5Sneaky Snitch", AdvancementFrameType.GOAL, getAdvancementByName("walk_100_km"), 0F, yOffset + 2, "§cCrouch 25 kilometers.", 25000);

        createAdvancement(WCShootAdvancement.class, "shoot_5000_times", Material.BOW, "§5Triggerhappy", AdvancementFrameType.GOAL, getAdvancementByName("settings_menu"), 2F, yOffset + 2, "§cShoot a Bow, Crossbow or Trident 5000 Times.", 5000);
    }

    private <T extends BaseAdvancement> void createAdvancement(Class<T> clazz, String id, Material icon, String title, AdvancementFrameType frameType, Advancement parent, float x, float y, String description, int maxProgression) {
        try {
            T advancement = clazz.getConstructor(String.class, AdvancementDisplay.class, Advancement.class, int.class)
                    .newInstance(id, new AdvancementDisplay(icon, title, frameType, true, true, x, y, description), parent, maxProgression);
            BASE_ADVANCEMENTS.put(id, advancement);
        } catch (Exception e) {
            throw new RuntimeException("Failed to create and register advancement " + id, e);
        }
    }

    private <T extends BaseAdvancement> void createAdvancement(Class<T> clazz, String id, ItemStack icon, String title, AdvancementFrameType frameType, Advancement parent, float x, float y, String description, int maxProgression) {
        try {
            T advancement = clazz.getConstructor(String.class, AdvancementDisplay.class, Advancement.class, int.class)
                    .newInstance(id, new AdvancementDisplay(icon, title, frameType, true, true, x, y, description), parent, maxProgression);
            BASE_ADVANCEMENTS.put(id, advancement);
        } catch (Exception e) {
            throw new RuntimeException("Failed to create and register advancement " + id, e);
        }
    }

    public AdvancementChecks getAdvancementChecks(){
        return advancementChecks;
    }

}

If you need something more, tell me.

frengor commented 10 months ago

I don't see anything problematic here. Can you also send the code of the advancement classes (please use a pastebin)? You can also send it privately to me on Discord if you don't feel comfortable sharing it publicly.

NickCloudAT commented 10 months ago

I don't see anything problematic here. Can you also send the code of the advancement classes (please use a pastebin)? You can also send it privately to me on Discord if you don't feel comfortable sharing it publicly.

Hey, sure. Would you mind just texting/adding me real quick? nickcloud on discord.

frengor commented 10 months ago

The issue is caused by some "large" advancements (with max progression in the thousands).

The solution is to show a completion percentage instead of the raw stat. The following code allows to do that without touching the max progression.

public class PercentageAdvancement extends BaseAdvancement {

    @LazyValue
    private AdvancementWrapper wrapper;

    public PercentageAdvancement(@NotNull String key, @NotNull AdvancementDisplay display, @NotNull Advancement parent) {
        super(key, display, parent);
    }

    public PercentageAdvancement(@NotNull String key, @NotNull AdvancementDisplay display, @NotNull Advancement parent, @Range(from = 1, to = Integer.MAX_VALUE) int maxProgression) {
        super(key, display, parent, maxProgression);
    }

    @Override
    @NotNull
    public AdvancementWrapper getNMSWrapper() {
        if (wrapper != null) {
            return wrapper;
        }

        try {
            return wrapper = AdvancementWrapper.craftBaseAdvancement(key.getNMSWrapper(), parent.getNMSWrapper(), display.getNMSWrapper(this), 100);
        } catch (ReflectiveOperationException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void onUpdate(@NotNull TeamProgression teamProgression, @NotNull Map<AdvancementWrapper, Integer> addedAdvancements) {
        if (isVisible(teamProgression)) {
            addedAdvancements.put(getNMSWrapper(), (getProgression(teamProgression) * 100) / maxProgression);
        }
    }
}
Brief code explanation Since we're only interested in changing the showed max progression to be in the 0-100 range, we can change it only in packets! We override the `getNMSWrapper()` method to create an advancement wrapper with max progression 100 and calculate the team's percentage in `onUpdate(...)`.