From 42fb37d996f78395910f7a8461d655070739cae4 Mon Sep 17 00:00:00 2001 From: Kli Kli Date: Wed, 31 Jul 2024 10:05:46 +0200 Subject: [PATCH] feat: improve unlock state sync backlog handling - thanks @Xaikii Closes #237 --- .../bookstate/BookUnlockStateManager.java | 57 ++++++++----------- .../modonomicon/ModonomiconFabric.java | 6 ++ .../modonomicon/ModonomiconForge.java | 5 ++ .../modonomicon/ModonomiconNeo.java | 6 ++ 4 files changed, 42 insertions(+), 32 deletions(-) diff --git a/common/src/main/java/com/klikli_dev/modonomicon/bookstate/BookUnlockStateManager.java b/common/src/main/java/com/klikli_dev/modonomicon/bookstate/BookUnlockStateManager.java index 8ecc5fb0..ec5a4dae 100644 --- a/common/src/main/java/com/klikli_dev/modonomicon/bookstate/BookUnlockStateManager.java +++ b/common/src/main/java/com/klikli_dev/modonomicon/bookstate/BookUnlockStateManager.java @@ -16,23 +16,25 @@ import com.klikli_dev.modonomicon.networking.RequestSyncBookStatesMessage; import com.klikli_dev.modonomicon.networking.SyncBookUnlockStatesMessage; import com.klikli_dev.modonomicon.platform.Services; +import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet; import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.players.PlayerList; import net.minecraft.util.datafix.DataFixTypes; import net.minecraft.world.entity.player.Player; import net.minecraft.world.level.saveddata.SavedData; import java.util.List; -import java.util.TimerTask; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; +import java.util.Set; +import java.util.UUID; public class BookUnlockStateManager { private static final BookUnlockStateManager instance = new BookUnlockStateManager(); + private final Set syncRequestedPlayers = new ObjectLinkedOpenHashSet<>(); public BookStatesSaveData saveData; - protected ScheduledExecutorService updateAndSyncTimer; + private boolean wasLoaded = false; public static BookUnlockStateManager get() { return instance; @@ -66,33 +68,8 @@ public void updateAndSyncFor(ServerPlayer player) { this.saveData.setDirty(); this.syncFor(player); } else { - //we have some edge cases where RecipesUpdatedEvent is fired after EntityJoinLevelEvent. - //in SP this means that books are not built yet when updateAndSyncFor is called for the first time. - //so we poll until it is available. - - //if timer already shut down, set to null so a new one will be created - if (this.updateAndSyncTimer != null && this.updateAndSyncTimer.isShutdown()) { - this.updateAndSyncTimer = null; - } - - //if we don't have a timer yet, create one - if (this.updateAndSyncTimer == null) { - this.updateAndSyncTimer = Executors.newSingleThreadScheduledExecutor(); - } - - final var currentTimer = this.updateAndSyncTimer; - //then schedule a task to run in 5 seconds - this.updateAndSyncTimer.schedule(new TimerTask() { - @Override - public void run() { - player.server.execute(() -> { - BookUnlockStateManager.this.updateAndSyncFor(player); - - //we also shut down the timer to free the thread, as this is only rarely used. - currentTimer.shutdown(); - }); - } - }, 5, TimeUnit.SECONDS); + this.syncRequestedPlayers.add(player.getUUID()); + this.wasLoaded = false; } } @@ -171,4 +148,20 @@ private void getSaveDataIfNecessary(Player player) { } } } + + public void onServerTickEnd(MinecraftServer server) { + if (server.getTickCount() % 100 != 0) return; //We only update every 5 seconds (100 ticks) + boolean newState = BookDataManager.get().areBooksBuilt(); + if (newState != this.wasLoaded) { // we only check for things if the state changed for some reason. + if (!this.wasLoaded && !this.syncRequestedPlayers.isEmpty()) { //we only process players if we have any and the state was correct. + PlayerList list = server.getPlayerList(); + for (UUID id : this.syncRequestedPlayers) { + ServerPlayer player = list.getPlayer(id); + if (player != null) this.updateAndSyncFor(player); + } + this.syncRequestedPlayers.clear(); + } + this.wasLoaded = newState; + } + } } diff --git a/fabric/src/main/java/com/klikli_dev/modonomicon/ModonomiconFabric.java b/fabric/src/main/java/com/klikli_dev/modonomicon/ModonomiconFabric.java index a2a758aa..25ff1a56 100644 --- a/fabric/src/main/java/com/klikli_dev/modonomicon/ModonomiconFabric.java +++ b/fabric/src/main/java/com/klikli_dev/modonomicon/ModonomiconFabric.java @@ -20,6 +20,7 @@ import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerWorldEvents; import net.fabricmc.fabric.api.event.player.UseBlockCallback; import net.fabricmc.fabric.api.itemgroup.v1.ItemGroupEvents; @@ -92,6 +93,11 @@ public void onInitialize() { UseBlockCallback.EVENT.register(LecternIntegration::rightClick); + //We use server tick to flush the queue of players that need a book state sync + ServerTickEvents.END_SERVER_TICK.register((server) -> { + BookUnlockStateManager.get().onServerTickEnd(server); + }); + //Advancement event handling for condition/unlock system //done in MixinPlayerAdvancements, because we have no event in Fabric diff --git a/forge/src/main/java/com/klikli_dev/modonomicon/ModonomiconForge.java b/forge/src/main/java/com/klikli_dev/modonomicon/ModonomiconForge.java index 3cd0e007..8fc35223 100644 --- a/forge/src/main/java/com/klikli_dev/modonomicon/ModonomiconForge.java +++ b/forge/src/main/java/com/klikli_dev/modonomicon/ModonomiconForge.java @@ -114,6 +114,11 @@ public ModonomiconForge() { //Advancement event handling for condition/unlock system MinecraftForge.EVENT_BUS.addListener((AdvancementEvent.AdvancementEarnEvent e) -> BookUnlockStateManager.get().onAdvancement((ServerPlayer) e.getEntity())); + //We use server tick to flush the queue of players that need a book state sync + MinecraftForge.EVENT_BUS.addListener(((TickEvent.ServerTickEvent.Post e) -> { + BookUnlockStateManager.get().onServerTickEnd(e.getServer()); + })); + //Datagen modEventBus.addListener(DataGenerators::gatherData); diff --git a/neo/src/main/java/com/klikli_dev/modonomicon/ModonomiconNeo.java b/neo/src/main/java/com/klikli_dev/modonomicon/ModonomiconNeo.java index a4a6af7f..3ca99b23 100644 --- a/neo/src/main/java/com/klikli_dev/modonomicon/ModonomiconNeo.java +++ b/neo/src/main/java/com/klikli_dev/modonomicon/ModonomiconNeo.java @@ -45,6 +45,7 @@ import net.neoforged.neoforge.event.entity.player.AdvancementEvent; import net.neoforged.neoforge.event.entity.player.PlayerInteractEvent; import net.neoforged.neoforge.event.level.LevelEvent; +import net.neoforged.neoforge.event.tick.ServerTickEvent; @Mod(Modonomicon.MOD_ID) public class ModonomiconNeo { @@ -114,6 +115,11 @@ public ModonomiconNeo(IEventBus modEventBus, ModContainer modContainer) { //Advancement event handling for condition/unlock system NeoForge.EVENT_BUS.addListener((AdvancementEvent.AdvancementEarnEvent e) -> BookUnlockStateManager.get().onAdvancement((ServerPlayer) e.getEntity())); + //We use server tick to flush the queue of players that need a book state sync + NeoForge.EVENT_BUS.addListener(((ServerTickEvent.Post e) -> { + BookUnlockStateManager.get().onServerTickEnd(e.getServer()); + })); + //Datagen modEventBus.addListener(DataGenerators::gatherData);