From 566b536c8db750950c058bec7940e483d4d6b74c Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sat, 28 Sep 2024 08:33:19 -0500 Subject: [PATCH] Add Api world load/unload events and DhApiWorldProxy.get/setReadOnly() --- .../interfaces/world/IDhApiWorldProxy.java | 27 +++++++- .../abstractEvents/DhApiWorldLoadEvent.java | 65 +++++++++++++++++++ .../abstractEvents/DhApiWorldUnloadEvent.java | 64 ++++++++++++++++++ .../core/api/internal/SharedApi.java | 20 +++++- .../core/generation/WorldGenerationQueue.java | 3 +- .../core/level/WorldGenModule.java | 9 ++- .../AbstractFullDataNetworkRequestQueue.java | 6 ++ .../core/world/AbstractDhWorld.java | 14 +++- .../core/world/DhApiWorldProxy.java | 63 ++++++++++++++++-- .../world/ILevelWrapper.java | 7 +- 10 files changed, 259 insertions(+), 19 deletions(-) create mode 100644 api/src/main/java/com/seibel/distanthorizons/api/methods/events/abstractEvents/DhApiWorldLoadEvent.java create mode 100644 api/src/main/java/com/seibel/distanthorizons/api/methods/events/abstractEvents/DhApiWorldUnloadEvent.java diff --git a/api/src/main/java/com/seibel/distanthorizons/api/interfaces/world/IDhApiWorldProxy.java b/api/src/main/java/com/seibel/distanthorizons/api/interfaces/world/IDhApiWorldProxy.java index 8daba387a..e95cb82df 100644 --- a/api/src/main/java/com/seibel/distanthorizons/api/interfaces/world/IDhApiWorldProxy.java +++ b/api/src/main/java/com/seibel/distanthorizons/api/interfaces/world/IDhApiWorldProxy.java @@ -24,14 +24,39 @@ package com.seibel.distanthorizons.api.interfaces.world; * A world is equivalent to a single server connection or a singleplayer world. * * @author James Seibel - * @version 2022-11-20 + * @version 2024-9-27 * @since API 1.0.0 */ public interface IDhApiWorldProxy { + //===================// + // getters / setters // + //===================// + /** Returns true if a world is loaded. */ boolean worldLoaded(); + /** + * Defaults to false.
+ * Setting this to true will prevent DH from updating or creating new LODs. + * + * @since API 4.0.0 + * @see IDhApiWorldProxy#getReadOnly() + * @throws IllegalStateException if no world is loaded + */ + void setReadOnly(boolean readOnly) throws IllegalStateException; + /** + * @since API 4.0.0 + * @see IDhApiWorldProxy#setReadOnly(boolean) + * @throws IllegalStateException if no world is loaded + */ + boolean getReadOnly() throws IllegalStateException; + + + + //================// + // level handlers // + //================// /** * In singleplayer this will return the level the player is currently in.
diff --git a/api/src/main/java/com/seibel/distanthorizons/api/methods/events/abstractEvents/DhApiWorldLoadEvent.java b/api/src/main/java/com/seibel/distanthorizons/api/methods/events/abstractEvents/DhApiWorldLoadEvent.java new file mode 100644 index 000000000..310b2df5c --- /dev/null +++ b/api/src/main/java/com/seibel/distanthorizons/api/methods/events/abstractEvents/DhApiWorldLoadEvent.java @@ -0,0 +1,65 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020-2023 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.distanthorizons.api.methods.events.abstractEvents; + +import com.seibel.distanthorizons.api.interfaces.world.IDhApiLevelWrapper; +import com.seibel.distanthorizons.api.interfaces.world.IDhApiWorldProxy; +import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEvent; +import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEventParam; +import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiEventParam; + +/** + * Called after Distant Horizons finishes loading a new level.
+ * Note: this may be fired before Minecraft has loaded in the player. + * + * @see IDhApiWorldProxy + * + * @author James Seibel + * @version 2024-9-27 + * @since API 4.0.0 + */ +public abstract class DhApiWorldLoadEvent implements IDhApiEvent +{ + /** Fired after Distant Horizons loads a new level. */ + public abstract void onLevelLoad(DhApiEventParam input); + + + //=========================// + // internal DH API methods // + //=========================// + + @Override + public final void fireEvent(DhApiEventParam input) { this.onLevelLoad(input); } + + + //==================// + // parameter object // + //==================// + + public static class EventParam implements IDhApiEventParam + { + public EventParam() { } + + + @Override + public EventParam copy() { return new EventParam(); } + } + +} \ No newline at end of file diff --git a/api/src/main/java/com/seibel/distanthorizons/api/methods/events/abstractEvents/DhApiWorldUnloadEvent.java b/api/src/main/java/com/seibel/distanthorizons/api/methods/events/abstractEvents/DhApiWorldUnloadEvent.java new file mode 100644 index 000000000..5276fff31 --- /dev/null +++ b/api/src/main/java/com/seibel/distanthorizons/api/methods/events/abstractEvents/DhApiWorldUnloadEvent.java @@ -0,0 +1,64 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020-2023 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.distanthorizons.api.methods.events.abstractEvents; + +import com.seibel.distanthorizons.api.interfaces.world.IDhApiLevelWrapper; +import com.seibel.distanthorizons.api.interfaces.world.IDhApiWorldProxy; +import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEvent; +import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEventParam; +import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiEventParam; + +/** + * Called after Distant Horizons has finished unloading a level. + * + * @see IDhApiWorldProxy + * + * @author James Seibel + * @version 2024-9-27 + * @since API 4.0.0 + */ +public abstract class DhApiWorldUnloadEvent implements IDhApiEvent +{ + /** Fired before Distant Horizons unloads a level. */ + public abstract void onLevelUnload(DhApiEventParam input); + + + //=========================// + // internal DH API methods // + //=========================// + + @Override + public final void fireEvent(DhApiEventParam input) { this.onLevelUnload(input); } + + + //==================// + // parameter object // + //==================// + + public static class EventParam implements IDhApiEventParam + { + public EventParam() { } + + + @Override + public DhApiWorldLoadEvent.EventParam copy() { return new DhApiWorldLoadEvent.EventParam(); } + } + +} \ No newline at end of file diff --git a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java index 97b6ba83f..d68ed239b 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java @@ -19,6 +19,8 @@ package com.seibel.distanthorizons.core.api.internal; +import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiWorldLoadEvent; +import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiWorldUnloadEvent; import com.seibel.distanthorizons.core.Initializer; import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; @@ -39,6 +41,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftCli import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; +import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector; import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.Nullable; @@ -89,6 +92,8 @@ public class SharedApi if (currentWorld != null) { ThreadPoolUtil.setupThreadPools(); + + ApiEventInjector.INSTANCE.fireAllEvents(DhApiWorldLoadEvent.class, new DhApiWorldLoadEvent.EventParam()); } else { @@ -102,6 +107,11 @@ public class SharedApi // recommend that the garbage collector cleans up any objects from the old world and thread pools System.gc(); + + ApiEventInjector.INSTANCE.fireAllEvents(DhApiWorldUnloadEvent.class, new DhApiWorldUnloadEvent.EventParam()); + + // fired after the unload event so API users can't change the read-only for any new worlds + DhApiWorldProxy.INSTANCE.setReadOnly(false, false); } } @@ -170,6 +180,13 @@ public class SharedApi return; } + // ignore updates if the world is read-only + if (DhApiWorldProxy.INSTANCE.getReadOnly()) + { + return; + } + + // only continue if the level is loaded IDhLevel dhLevel = dhWorld.getLevel(level); if (dhLevel == null) @@ -261,10 +278,7 @@ public class SharedApi } } } - - /** returning a {@link CompletableFuture} isn't necessary, but allows Intellij to properly show the full stack trace when debugging. */ - @SuppressWarnings("UnusedReturnValue") private static void processQueuedChunkUpdate() { //LOGGER.trace(chunkWrapper.getChunkPos() + " " + executor.getActiveCount() + " / " + executor.getQueue().size() + " - " + executor.getCompletedTaskCount()); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java b/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java index 67e90d450..36123a2da 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java @@ -40,6 +40,7 @@ import com.seibel.distanthorizons.core.util.objects.DataCorruptedException; import com.seibel.distanthorizons.core.util.objects.UncheckedInterruptedException; import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; +import com.seibel.distanthorizons.core.world.DhApiWorldProxy; import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory; import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; @@ -211,7 +212,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb try { // loop until the generator is shutdown - while (!Thread.interrupted()) + while (!Thread.interrupted() && !DhApiWorldProxy.INSTANCE.getReadOnly()) { this.generator.preGeneratorTaskStart(); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/WorldGenModule.java b/core/src/main/java/com/seibel/distanthorizons/core/level/WorldGenModule.java index 9b0c4716d..847b186e5 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/WorldGenModule.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/WorldGenModule.java @@ -24,6 +24,7 @@ import com.seibel.distanthorizons.core.generation.IFullDataSourceRetrievalQueue; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.f3.F3Screen; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D; +import com.seibel.distanthorizons.core.world.DhApiWorldProxy; import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.Nullable; @@ -60,7 +61,7 @@ public class WorldGenModule implements Closeable GeneratedFullDataSourceProvider.IOnWorldGenCompleteListener onWorldGenCompleteListener, GeneratedFullDataSourceProvider dataSourceProvider, Supplier worldGenStateSupplier - ) + ) { this.onWorldGenCompleteListener = onWorldGenCompleteListener; this.dataSourceProvider = dataSourceProvider; @@ -111,6 +112,8 @@ public class WorldGenModule implements Closeable public void worldGenTick() { boolean shouldDoWorldGen = this.onWorldGenCompleteListener.shouldDoWorldGen(); + // if the world is read only don't generate anything + shouldDoWorldGen &= !DhApiWorldProxy.INSTANCE.getReadOnly(); boolean isWorldGenRunning = this.isWorldGenRunning(); if (shouldDoWorldGen && !isWorldGenRunning) @@ -188,8 +191,8 @@ public class WorldGenModule implements Closeable String waitingCountStr = F3Screen.NUMBER_FORMAT.format(worldGenState.worldGenerationQueue.getWaitingTaskCount()); String inProgressCountStr = F3Screen.NUMBER_FORMAT.format(worldGenState.worldGenerationQueue.getInProgressTaskCount()); String totalCountEstimateStr = F3Screen.NUMBER_FORMAT.format(worldGenState.worldGenerationQueue.getEstimatedTotalTaskCount()); - messageList.add("World Gen Tasks: "+waitingCountStr+"/"+totalCountEstimateStr+" (in progress: "+inProgressCountStr+")"); - + messageList.add("World Gen Tasks: ${waitingCountStr}/${totalCountEstimateStr} (in progress: ${inProgressCountStr})"); + worldGenState.worldGenerationQueue.addDebugMenuStringsToList(messageList); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/client/AbstractFullDataNetworkRequestQueue.java b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/client/AbstractFullDataNetworkRequestQueue.java index b162ba5ad..feb720750 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/client/AbstractFullDataNetworkRequestQueue.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/client/AbstractFullDataNetworkRequestQueue.java @@ -24,6 +24,7 @@ import com.seibel.distanthorizons.core.util.TimerUtil; import com.seibel.distanthorizons.core.util.objects.DataCorruptedException; import com.seibel.distanthorizons.core.util.ratelimiting.SupplierBasedRateLimiter; import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; +import com.seibel.distanthorizons.core.world.DhApiWorldProxy; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import org.apache.logging.log4j.LogManager; @@ -133,6 +134,11 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende public synchronized boolean tick(DhBlockPos2D targetPos) { + if (DhApiWorldProxy.INSTANCE.getReadOnly()) + { + return false; + } + if (this.closingFuture != null || !this.networkState.isReady()) { return false; diff --git a/core/src/main/java/com/seibel/distanthorizons/core/world/AbstractDhWorld.java b/core/src/main/java/com/seibel/distanthorizons/core/world/AbstractDhWorld.java index ffa051290..17615a238 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/world/AbstractDhWorld.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/world/AbstractDhWorld.java @@ -59,6 +59,18 @@ public abstract class AbstractDhWorld implements IDhWorld, Closeable * by overriding children. */ public void addDebugMenuStringsToList(List messageList) - { messageList.add(this.environment + " World with " + F3Screen.NUMBER_FORMAT.format(this.getLoadedLevelCount()) + " levels"); } + { + EWorldEnvironment environment = this.environment; + String levelCountStr = F3Screen.NUMBER_FORMAT.format(this.getLoadedLevelCount()); + + String readOnlyStr = ""; + if (DhApiWorldProxy.INSTANCE.getReadOnly()) + { + readOnlyStr += " - ReadOnly"; + } + + String message = "${environment} World with ${levelCountStr} levels${readOnlyStr}"; + messageList.add(message); + } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/world/DhApiWorldProxy.java b/core/src/main/java/com/seibel/distanthorizons/core/world/DhApiWorldProxy.java index 3d61c026c..ce17bf93c 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/world/DhApiWorldProxy.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/world/DhApiWorldProxy.java @@ -25,9 +25,11 @@ import com.seibel.distanthorizons.api.interfaces.world.IDhApiWorldProxy; import com.seibel.distanthorizons.core.api.internal.SharedApi; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.level.IDhLevel; +import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; +import org.apache.logging.log4j.Logger; import java.util.ArrayList; @@ -38,16 +40,20 @@ import java.util.ArrayList; * to be loaded/unloaded. * * @author James Seibel - * @version 2022-11-20 + * @version 2024-9-27 */ public class DhApiWorldProxy implements IDhApiWorldProxy { public static DhApiWorldProxy INSTANCE = new DhApiWorldProxy(); + private static final Logger LOGGER = DhLoggerBuilder.getLogger(); + private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); private static final IMinecraftSharedWrapper MC_SHARED = SingletonInjector.INSTANCE.get(IMinecraftSharedWrapper.class); private static final String NO_WORLD_EXCEPTION_STRING = "No world loaded"; + private boolean isReadOnly = false; + //=============// @@ -58,13 +64,61 @@ public class DhApiWorldProxy implements IDhApiWorldProxy - //=========// - // methods // - //=========// + //===================// + // getters / setters // + //===================// @Override public boolean worldLoaded() { return SharedApi.getAbstractDhWorld() != null; } + @Override + public void setReadOnly(boolean readOnly) { this.setReadOnly(readOnly, true); } + /** + * Not part of the public API. + * Normal API users shouldn't be able to change the upcoming world state + * this is only here so DH can revert the readonly value after the world is unloaded + */ + public void setReadOnly(boolean readOnly, boolean throwIfWorldUnloaded) + { + if (throwIfWorldUnloaded && SharedApi.getAbstractDhWorld() == null) + { + throw new IllegalStateException(NO_WORLD_EXCEPTION_STRING); + } + + boolean valueChanged = (this.isReadOnly != readOnly); + this.isReadOnly = readOnly; + + if (valueChanged) + { + if (this.isReadOnly) + { + LOGGER.info("DH world set to read-only. LODs will not update while this API flag is active."); + } + else + { + LOGGER.info("DH world is no longer in read-only mode. LODs will update like normal."); + } + } + } + + @Override + public boolean getReadOnly() + { + if (SharedApi.getAbstractDhWorld() == null) + { + throw new IllegalStateException(NO_WORLD_EXCEPTION_STRING); + } + + + return this.isReadOnly; + } + + + + //================// + // level handlers // + //================// + @Override public IDhApiLevelWrapper getSinglePlayerLevel() { @@ -146,4 +200,5 @@ public class DhApiWorldProxy implements IDhApiWorldProxy return returnList; } + } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/world/ILevelWrapper.java b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/world/ILevelWrapper.java index 907c68b25..550a88428 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/world/ILevelWrapper.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/world/ILevelWrapper.java @@ -27,12 +27,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrappe import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable; -/** - * Can be either a Server world or a Client world. - * - * @author James Seibel - * @version 2023-6-17 - */ +/** Can be either a Server world or a Client world. */ public interface ILevelWrapper extends IDhApiLevelWrapper, IBindable {