From dbf3c828d24cba094ef118b6e5aa1086a3db2b17 Mon Sep 17 00:00:00 2001 From: bloodshot Date: Sun, 5 Feb 2017 20:34:07 -0500 Subject: [PATCH] Fix various issues with mod dimensions. * Change keepSpawnLoaded, generateSpawnOnLoad, and loadOnStartup to Boolean's to determine when nothing is set. * Move PlayerChunkMap chunk unload queue with no players to start of server tick. This avoids chunks from unloading when requested before PlayerChunkMap tick. * Always allow chunks to load when requested from getTileEntity. This fixes issues with mods such as RFToolsDimensions that check for a specific TE to perform logic. * Remove unregisterable dimension code as it breaks Forge. * Fix wrong foldername being used for hot-loaded dimensions. Fixes #910, Fixes #920, Fixes #995, Fixes #1046 Fixes #1109, Fixes #1117, Fixes #1153 --- .../common/config/category/WorldCategory.java | 18 +++---- .../common/interfaces/world/IMixinWorld.java | 2 + .../core/server/MixinMinecraftServer.java | 36 +++++++++---- .../management/MixinPlayerChunkMap.java | 10 ++++ .../core/world/storage/MixinWorldInfo.java | 45 ++++++++++++++--- .../MixinWorld_Inline_Valid_BlockPos.java | 10 +--- .../common/world/WorldManager.java | 50 ++++++++----------- 7 files changed, 107 insertions(+), 64 deletions(-) diff --git a/src/main/java/org/spongepowered/common/config/category/WorldCategory.java b/src/main/java/org/spongepowered/common/config/category/WorldCategory.java index cdae3991cd1..636b610124c 100644 --- a/src/main/java/org/spongepowered/common/config/category/WorldCategory.java +++ b/src/main/java/org/spongepowered/common/config/category/WorldCategory.java @@ -50,13 +50,13 @@ public class WorldCategory extends ConfigCategory { protected boolean worldEnabled = true; @Setting(value = "load-on-startup", comment = "Enable if this world should be loaded on startup.") - protected boolean loadOnStartup = true; + protected Boolean loadOnStartup; @Setting(value = "generate-spawn-on-load", comment = "Enable if you want the world to generate spawn the moment its loaded.") - protected boolean generateSpawnOnLoad = true; + protected Boolean generateSpawnOnLoad; @Setting(value = "keep-spawn-loaded", comment = "Enable if this world's spawn should remain loaded with no players.") - protected boolean keepSpawnLoaded = true; + protected Boolean keepSpawnLoaded; @Setting(value = "pvp-enabled", comment = "Enable if this world allows PVP combat.") protected boolean pvpEnabled = true; @@ -145,19 +145,19 @@ public void setChunkUnloadDelay(int delay) { this.chunkUnloadDelay = delay; } - public boolean loadOnStartup() { + public Boolean loadOnStartup() { return this.loadOnStartup; } - public void setLoadOnStartup(boolean state) { + public void setLoadOnStartup(Boolean state) { this.loadOnStartup = state; } - public boolean getKeepSpawnLoaded() { + public Boolean getKeepSpawnLoaded() { return this.keepSpawnLoaded; } - public void setKeepSpawnLoaded(boolean loaded) { + public void setKeepSpawnLoaded(Boolean loaded) { this.keepSpawnLoaded = loaded; } @@ -169,11 +169,11 @@ public void setPVPEnabled(boolean allow) { this.pvpEnabled = allow; } - public boolean getGenerateSpawnOnLoad() { + public Boolean getGenerateSpawnOnLoad() { return this.generateSpawnOnLoad; } - public void setGenerateSpawnOnLoad(boolean allow) { + public void setGenerateSpawnOnLoad(Boolean allow) { this.generateSpawnOnLoad = allow; } diff --git a/src/main/java/org/spongepowered/common/interfaces/world/IMixinWorld.java b/src/main/java/org/spongepowered/common/interfaces/world/IMixinWorld.java index 654a85cb9ad..315bd51bd7b 100644 --- a/src/main/java/org/spongepowered/common/interfaces/world/IMixinWorld.java +++ b/src/main/java/org/spongepowered/common/interfaces/world/IMixinWorld.java @@ -34,6 +34,8 @@ public interface IMixinWorld { void setWeatherStartTime(long weatherStartTime); + void setCallingWorldEvent(boolean flag); + @Nullable EntityPlayer getClosestPlayerToEntityWhoAffectsSpawning(net.minecraft.entity.Entity entity, double d1tance); diff --git a/src/main/java/org/spongepowered/common/mixin/core/server/MixinMinecraftServer.java b/src/main/java/org/spongepowered/common/mixin/core/server/MixinMinecraftServer.java index fe64c724bb6..d83c09fb755 100644 --- a/src/main/java/org/spongepowered/common/mixin/core/server/MixinMinecraftServer.java +++ b/src/main/java/org/spongepowered/common/mixin/core/server/MixinMinecraftServer.java @@ -48,7 +48,6 @@ import net.minecraft.world.WorldServer; import net.minecraft.world.WorldType; import net.minecraft.world.storage.ISaveHandler; -import net.minecraft.world.storage.WorldInfo; import org.apache.logging.log4j.Logger; import org.spongepowered.api.Server; import org.spongepowered.api.Sponge; @@ -329,7 +328,7 @@ protected void loadAllWorlds(String overworldFolder, String worldName, long seed WorldManager.loadAllWorlds(worldName, seed, type, generatorOptions); - this.getPlayerList().setPlayerManager(new WorldServer[]{WorldManager.getWorldByDimensionId(0).get()}); + this.getPlayerList().setPlayerManager(this.worldServers); this.setDifficultyForAllWorlds(this.getDifficulty()); this.initialWorldChunkLoad(); } @@ -569,16 +568,31 @@ public String toString() { return getClass().getSimpleName(); } - @Redirect(method = "updateTimeLightAndEntities", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/WorldServer;getWorldInfo()Lnet/minecraft/world/storage/WorldInfo;")) - public WorldInfo onUpdateTimeLightAndEntitiesGetWorld(WorldServer worldServer) { - // ChunkGC needs to be processed before a world tick in order to guarantee any chunk queued for unload - // can still be marked active and avoid unload if accessed during the same tick. - // Note: This injection must come before Forge's pre world tick event or it will cause issues with mods. - IMixinWorldServer spongeWorld = (IMixinWorldServer) worldServer; - if (spongeWorld.getChunkGCTickInterval() > 0) { - spongeWorld.doChunkGC(); + // All chunk unload queuing needs to be processed BEFORE the future tasks are run as mods/plugins may have tasks that request chunks. + // This prevents a situation where a chunk is requested to load then unloads at end of tick. + @Inject(method = "updateTimeLightAndEntities", at = @At("HEAD")) + public void onUpdateTimeLightAndEntitiesHead(CallbackInfo ci) { + for (int i = 0; i < this.worldServers.length; ++i) + { + WorldServer worldServer = this.worldServers[i]; + // ChunkGC needs to be processed before a world tick in order to guarantee any chunk queued for unload + // can still be marked active and avoid unload if accessed during the same tick. + // Note: This injection must come before Forge's pre world tick event or it will cause issues with mods. + IMixinWorldServer spongeWorld = (IMixinWorldServer) worldServer; + if (spongeWorld.getChunkGCTickInterval() > 0) { + spongeWorld.doChunkGC(); + } + // Moved from PlayerChunkMap to avoid chunks from unloading after being requested in same tick + if (worldServer.getPlayerChunkMap().players.isEmpty()) + { + WorldProvider worldprovider = worldServer.provider; + + if (!worldprovider.canRespawnHere()) + { + worldServer.getChunkProvider().unloadAllChunks(); + } + } } - return worldServer.getWorldInfo(); } @Redirect(method = "updateTimeLightAndEntities", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/WorldServer;getEntityTracker()Lnet/minecraft/entity/EntityTracker;")) diff --git a/src/main/java/org/spongepowered/common/mixin/core/server/management/MixinPlayerChunkMap.java b/src/main/java/org/spongepowered/common/mixin/core/server/management/MixinPlayerChunkMap.java index c9f145e0dbb..4f504519111 100644 --- a/src/main/java/org/spongepowered/common/mixin/core/server/management/MixinPlayerChunkMap.java +++ b/src/main/java/org/spongepowered/common/mixin/core/server/management/MixinPlayerChunkMap.java @@ -24,6 +24,7 @@ */ package org.spongepowered.common.mixin.core.server.management; +import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.server.management.PlayerChunkMap; import net.minecraft.server.management.PlayerChunkMapEntry; import net.minecraft.world.WorldServer; @@ -41,6 +42,8 @@ import org.spongepowered.common.interfaces.server.management.IMixinPlayerChunkMap; import org.spongepowered.common.interfaces.world.IMixinWorldServer; +import java.util.List; + import javax.annotation.Nullable; @Mixin(PlayerChunkMap.class) @@ -76,6 +79,13 @@ private void onUnloadChunk(ChunkProviderServer chunkProvider, Chunk chunk) { } } + @Redirect(method = "tick", at = @At(value = "INVOKE", target = "Ljava/util/List;isEmpty()Z", ordinal = 2)) + private boolean onChunkUnloadCheck(List playerList) { + // Queuing all chunks for unload when there are no players has been moved to start of tick in MixinMinecraftServer. + // This avoids chunks from reloading when any request to load a chunk is done before this call such as a mod requesting a TE. + return false; + } + @Inject(method = "tick", at = @At(value = "INVOKE", target = "Ljava/util/Set;iterator()Ljava/util/Iterator;", ordinal = 0)) public void onStartUpdateLoop(CallbackInfo ci) { this.isUpdatingInstances = true; diff --git a/src/main/java/org/spongepowered/common/mixin/core/world/storage/MixinWorldInfo.java b/src/main/java/org/spongepowered/common/mixin/core/world/storage/MixinWorldInfo.java index f9dfd05b2cd..71e7c706789 100644 --- a/src/main/java/org/spongepowered/common/mixin/core/world/storage/MixinWorldInfo.java +++ b/src/main/java/org/spongepowered/common/mixin/core/world/storage/MixinWorldInfo.java @@ -565,10 +565,24 @@ public void setEnabled(boolean enabled) { @Override public boolean loadOnStartup() { + Boolean loadOnStartup = null; if (!this.worldConfig.getConfig().isConfigEnabled()) { - return SpongeHooks.getActiveConfig(((IMixinDimensionType) this.dimensionType).getConfigPath(), this.getWorldName()).getConfig().getWorld().loadOnStartup(); + DimensionConfig dimConfig = ((IMixinDimensionType) this.dimensionType).getDimensionConfig().getConfig(); + if (dimConfig.isConfigEnabled()) { + loadOnStartup = dimConfig.getWorld().loadOnStartup(); + } else { + loadOnStartup = this.worldConfig.getConfig().getWorld().loadOnStartup(); + } + } else { + loadOnStartup = this.worldConfig.getConfig().getWorld().loadOnStartup(); + } + if (loadOnStartup == null) { + if (this.dimensionId != null) { + return ((IMixinDimensionType) this.dimensionType).shouldGenerateSpawnOnLoad(); + } + return false; } - return this.worldConfig.getConfig().getWorld().loadOnStartup(); + return loadOnStartup; } @Override @@ -578,10 +592,21 @@ public void setLoadOnStartup(boolean state) { @Override public boolean doesKeepSpawnLoaded() { + Boolean keepSpawnLoaded = null; if (!this.worldConfig.getConfig().isConfigEnabled()) { - return SpongeHooks.getActiveConfig(((IMixinDimensionType) this.dimensionType).getConfigPath(), this.getWorldName()).getConfig().getWorld().getKeepSpawnLoaded(); + DimensionConfig dimConfig = ((IMixinDimensionType) this.dimensionType).getDimensionConfig().getConfig(); + if (dimConfig.isConfigEnabled()) { + keepSpawnLoaded = dimConfig.getWorld().getKeepSpawnLoaded(); + } else { + keepSpawnLoaded = this.worldConfig.getConfig().getWorld().getKeepSpawnLoaded(); + } + } else { + keepSpawnLoaded = this.worldConfig.getConfig().getWorld().getKeepSpawnLoaded(); } - return this.worldConfig.getConfig().getWorld().getKeepSpawnLoaded(); + if (keepSpawnLoaded == null) { + return ((IMixinDimensionType) this.dimensionType).shouldGenerateSpawnOnLoad(); + } + return keepSpawnLoaded; } @Override @@ -591,13 +616,21 @@ public void setKeepSpawnLoaded(boolean loaded) { @Override public boolean doesGenerateSpawnOnLoad() { + Boolean shouldGenerateSpawn = null; if (!this.worldConfig.getConfig().isConfigEnabled()) { DimensionConfig dimConfig = ((IMixinDimensionType) this.dimensionType).getDimensionConfig().getConfig(); if (dimConfig.isConfigEnabled()) { - return dimConfig.getWorld().getGenerateSpawnOnLoad(); + shouldGenerateSpawn = dimConfig.getWorld().getGenerateSpawnOnLoad(); + } else { + shouldGenerateSpawn = this.worldConfig.getConfig().getWorld().getGenerateSpawnOnLoad(); } + } else { + shouldGenerateSpawn = this.worldConfig.getConfig().getWorld().getGenerateSpawnOnLoad(); + } + if (shouldGenerateSpawn == null) { + return ((IMixinDimensionType) this.dimensionType).shouldGenerateSpawnOnLoad(); } - return this.worldConfig.getConfig().getWorld().getGenerateSpawnOnLoad(); + return shouldGenerateSpawn; } @Override diff --git a/src/main/java/org/spongepowered/common/mixin/optimization/world/MixinWorld_Inline_Valid_BlockPos.java b/src/main/java/org/spongepowered/common/mixin/optimization/world/MixinWorld_Inline_Valid_BlockPos.java index 908a78347bb..0b0145d0be9 100644 --- a/src/main/java/org/spongepowered/common/mixin/optimization/world/MixinWorld_Inline_Valid_BlockPos.java +++ b/src/main/java/org/spongepowered/common/mixin/optimization/world/MixinWorld_Inline_Valid_BlockPos.java @@ -57,9 +57,7 @@ public abstract class MixinWorld_Inline_Valid_BlockPos { @Shadow public abstract void notifyLightSet(BlockPos pos); @Shadow public abstract IChunkProvider getChunkProvider(); - @Shadow @Nullable private TileEntity getPendingTileEntityAt(BlockPos p_189508_1_) { - return null; // Shadowed - } + @Shadow @Nullable public abstract TileEntity getPendingTileEntityAt(BlockPos p_189508_1_); /** * @author gabizou - August 4th, 2016 @@ -99,12 +97,6 @@ public TileEntity getTileEntity(BlockPos pos) { } else { TileEntity tileentity = null; - // Sponge start - don't allow loading chunks here - if (!this.isBlockLoaded(pos)) { - return null; - } - // Sponge end - if (this.processingLoadedTiles) { tileentity = this.getPendingTileEntityAt(pos); } diff --git a/src/main/java/org/spongepowered/common/world/WorldManager.java b/src/main/java/org/spongepowered/common/world/WorldManager.java index 7c0fb2d96cd..210206d4a37 100644 --- a/src/main/java/org/spongepowered/common/world/WorldManager.java +++ b/src/main/java/org/spongepowered/common/world/WorldManager.java @@ -34,8 +34,6 @@ import com.google.common.collect.MapMaker; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.ints.IntOpenHashSet; -import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.objects.ObjectIterator; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.nbt.CompressedStreamTools; @@ -117,7 +115,6 @@ public final class WorldManager { private static final Int2ObjectMap dimensionTypeByTypeId = new Int2ObjectOpenHashMap<>(3); private static final Int2ObjectMap dimensionTypeByDimensionId = new Int2ObjectOpenHashMap<>(3); - private static final IntSet unregisterableDimensions = new IntOpenHashSet(3); private static final Int2ObjectMap dimensionPathByDimensionId = new Int2ObjectOpenHashMap<>(3); private static final Int2ObjectOpenHashMap worldByDimensionId = new Int2ObjectOpenHashMap<>(3); private static final Map worldPropertiesByFolderName = new HashMap<>(3); @@ -152,9 +149,9 @@ public static void registerVanillaTypesAndDimensions() { WorldManager.registerDimensionType(-1, DimensionType.NETHER); WorldManager.registerDimensionType(1, DimensionType.THE_END); - WorldManager.registerDimension(0, DimensionType.OVERWORLD, false); - WorldManager.registerDimension(-1, DimensionType.NETHER, false); - WorldManager.registerDimension(1, DimensionType.THE_END, false); + WorldManager.registerDimension(0, DimensionType.OVERWORLD); + WorldManager.registerDimension(-1, DimensionType.NETHER); + WorldManager.registerDimension(1, DimensionType.THE_END); } isVanillaRegistered = true; @@ -196,7 +193,7 @@ public static Integer getNextFreeDimensionId() { return dimensionBits.nextClearBit(0); } - public static boolean registerDimension(int dimensionId, DimensionType type, boolean canBeUnregistered) { + public static boolean registerDimension(int dimensionId, DimensionType type) { checkNotNull(type); if (!dimensionTypeByTypeId.containsValue(type)) { return false; @@ -209,9 +206,6 @@ public static boolean registerDimension(int dimensionId, DimensionType type, boo if (dimensionId >= 0) { dimensionBits.set(dimensionId); } - if (canBeUnregistered) { - unregisterableDimensions.add(dimensionId); - } return true; } @@ -234,8 +228,8 @@ public static void registerDimensionPath(int dimensionId, Path dimensionDataRoot dimensionPathByDimensionId.put(dimensionId, dimensionDataRoot); } - public static Optional getDimensionPath(int dimensionId) { - return Optional.ofNullable(dimensionPathByDimensionId.get(dimensionId)); + public static Path getDimensionPath(int dimensionId) { + return dimensionPathByDimensionId.get(dimensionId); } public static Optional getDimensionType(int dimensionId) { @@ -269,8 +263,8 @@ public static int[] getRegisteredDimensionIds() { return dimensionTypeByTypeId.keySet().toIntArray(); } - public static Optional getWorldFolder(DimensionType dimensionType, int dimensionId) { - return Optional.ofNullable(dimensionPathByDimensionId.get(dimensionId)); + public static Path getWorldFolder(DimensionType dimensionType, int dimensionId) { + return dimensionPathByDimensionId.get(dimensionId); } public static boolean isDimensionRegistered(int dimensionId) { @@ -355,7 +349,6 @@ public static void unregisterAllWorldSettings() { dimensionPathByDimensionId.clear(); dimensionBits.clear(); weakWorldByWorld.clear(); - unregisterableDimensions.clear(); isVanillaRegistered = false; // This is needed to ensure that DimensionType is usable by GuiListWorldSelection, which is only ever used when the server isn't running @@ -387,6 +380,10 @@ public static Optional getFolderForUuid(UUID uuid) { } public static WorldProperties createWorldProperties(String folderName, WorldArchetype archetype) { + return createWorldProperties(folderName, archetype, null); + } + + public static WorldProperties createWorldProperties(String folderName, WorldArchetype archetype, Integer dimensionId) { checkNotNull(folderName); checkNotNull(archetype); final Optional optWorldServer = getWorld(folderName); @@ -414,7 +411,9 @@ public static WorldProperties createWorldProperties(String folderName, WorldArch } setUuidOnProperties(getCurrentSavesDirectory().get(), (WorldProperties) worldInfo); - if (((IMixinWorldInfo) worldInfo).getDimensionId() == null || ((IMixinWorldInfo) worldInfo).getDimensionId() == Integer.MIN_VALUE) { + if (dimensionId != null) { + ((IMixinWorldInfo) worldInfo).setDimensionId(dimensionId); + } else if (((IMixinWorldInfo) worldInfo).getDimensionId() == null || ((IMixinWorldInfo) worldInfo).getDimensionId() == Integer.MIN_VALUE) { ((IMixinWorldInfo) worldInfo).setDimensionId(WorldManager.getNextFreeDimensionId()); } ((WorldProperties) worldInfo).setGeneratorType(archetype.getGeneratorType()); @@ -477,7 +476,7 @@ public static boolean unloadWorld(WorldServer worldServer, boolean checkConfig) // We only check config if base game wants to unload world. If mods/plugins say unload, we unload if (checkConfig) { - if (((IMixinWorldServer) worldServer).getActiveConfig().getConfig().getWorld().getKeepSpawnLoaded()) { + if (((WorldProperties) worldServer.getWorldInfo()).doesKeepSpawnLoaded()) { return false; } } @@ -505,7 +504,7 @@ public static boolean unloadWorld(WorldServer worldServer, boolean checkConfig) SpongeImpl.postEvent(SpongeEventFactory.createUnloadWorldEvent(Cause.of(NamedCause.source(server)), (org.spongepowered.api.world.World) worldServer)); - if (!server.isServerRunning() && unregisterableDimensions.contains(dimensionId)) { + if (!server.isServerRunning()) { unregisterDimension(dimensionId); } @@ -613,7 +612,7 @@ private static Optional loadWorld(String worldName, @Nullable ISave } final int dimensionId = ((IMixinWorldInfo) properties).getDimensionId(); - registerDimension(dimensionId, (DimensionType) (Object) properties.getDimensionType(), true); + registerDimension(dimensionId, (DimensionType) (Object) properties.getDimensionType()); registerDimensionPath(dimensionId, worldFolder); SpongeImpl.getLogger().info("Loading world [{}] ({})", properties.getWorldName(), getDimensionType (dimensionId).get().getName()); @@ -669,14 +668,13 @@ public static void loadAllWorlds(String worldName, long defaultSeed, WorldType d } // Step 1 - Grab the world's data folder - final Optional optWorldFolder = getWorldFolder(dimensionType, dimensionId); - if (!optWorldFolder.isPresent()) { + final Path worldFolder = getWorldFolder(dimensionType, dimensionId); + if (worldFolder == null) { SpongeImpl.getLogger().error("An attempt was made to load a world with dimension id [{}] that has no registered world folder!", dimensionId); continue; } - final Path worldFolder = optWorldFolder.get(); final String worldFolderName = worldFolder.getFileName().toString(); // Step 2 - See if we are allowed to load it @@ -967,15 +965,9 @@ private static void registerExistingSpongeDimensions(Path rootPath) { continue; } - if (isDimensionRegistered(dimensionId)) { - SpongeImpl.getLogger().error("Unable to register dim id ({}) from world folder [{}]. This dim id has already been registered " - + "from world folder [{}].", dimensionId, worldFolderName, worldFolderByDimensionId.get(dimensionId)); - continue; - } - worldFolderByDimensionId.put(dimensionId, worldFolderName); - registerDimension(dimensionId, (DimensionType)(Object) dimensionType, true); registerDimensionPath(dimensionId, rootPath.resolve(worldFolderName)); + registerDimension(dimensionId, (DimensionType)(Object) dimensionType); } } catch (IOException e) { e.printStackTrace();