diff --git a/patches/minecraft/net/minecraft/world/chunk/storage/AnvilChunkLoader.java.patch b/patches/minecraft/net/minecraft/world/chunk/storage/AnvilChunkLoader.java.patch index b656eb63c..ac5e784ce 100644 --- a/patches/minecraft/net/minecraft/world/chunk/storage/AnvilChunkLoader.java.patch +++ b/patches/minecraft/net/minecraft/world/chunk/storage/AnvilChunkLoader.java.patch @@ -1,16 +1,15 @@ --- a/net/minecraft/world/chunk/storage/AnvilChunkLoader.java +++ b/net/minecraft/world/chunk/storage/AnvilChunkLoader.java -@@ -225,6 +225,9 @@ +@@ -225,6 +225,8 @@ this.func_202156_a((ChunkPrimer)p_75816_2_, p_75816_1_, nbttagcompound1); } -+ net.minecraftforge.common.ForgeChunkManager.storeChunkNBT(p_75816_1_, p_75816_2_, nbttagcompound1); + net.minecraftforge.common.MinecraftForge.EVENT_BUS.post(new net.minecraftforge.event.world.ChunkDataEvent.Save(p_75816_2_, nbttagcompound)); + this.func_75824_a(chunkpos, nbttagcompound); } catch (Exception exception) { field_151505_a.error("Failed to save chunk", (Throwable)exception); -@@ -388,10 +391,14 @@ +@@ -388,10 +390,14 @@ for(int j = 0; j < p_75820_1_.func_177429_s().length; ++j) { for(Entity entity : p_75820_1_.func_177429_s()[j]) { NBTTagCompound nbttagcompound = new NBTTagCompound(); @@ -25,7 +24,7 @@ } } -@@ -402,7 +409,11 @@ +@@ -402,7 +408,11 @@ TileEntity tileentity = p_75820_1_.func_175625_s(blockpos); if (tileentity != null) { NBTTagCompound nbttagcompound1 = new NBTTagCompound(); @@ -38,7 +37,7 @@ nbttagcompound1.func_74757_a("keepPacked", false); nbttaglist2.add((INBTBase)nbttagcompound1); } else { -@@ -442,6 +453,16 @@ +@@ -442,6 +452,16 @@ p_75820_3_.func_74782_a("Heightmaps", nbttagcompound2); p_75820_3_.func_74782_a("Structures", this.func_202160_a(p_75820_1_.field_76635_g, p_75820_1_.field_76647_h, p_75820_1_.func_201609_c(), p_75820_1_.func_201604_d())); @@ -55,7 +54,7 @@ } private Chunk func_75823_a(IWorld p_75823_1_, NBTTagCompound p_75823_2_) { -@@ -508,6 +529,10 @@ +@@ -508,6 +528,10 @@ chunk.func_177427_f(true); } @@ -66,7 +65,7 @@ return chunk; } -@@ -809,4 +834,8 @@ +@@ -809,4 +833,8 @@ return flag; } diff --git a/patches/minecraft/net/minecraft/world/gen/ChunkProviderServer.java.patch b/patches/minecraft/net/minecraft/world/gen/ChunkProviderServer.java.patch index ce4a098ae..f289942f1 100644 --- a/patches/minecraft/net/minecraft/world/gen/ChunkProviderServer.java.patch +++ b/patches/minecraft/net/minecraft/world/gen/ChunkProviderServer.java.patch @@ -1,14 +1,6 @@ --- a/net/minecraft/world/gen/ChunkProviderServer.java +++ b/net/minecraft/world/gen/ChunkProviderServer.java -@@ -239,6 +239,7 @@ - Chunk chunk = this.field_73244_f.get(olong); - if (chunk != null) { - chunk.func_76623_d(); -+ net.minecraftforge.common.ForgeChunkManager.putDormantChunk(ChunkPos.func_77272_a(chunk.field_76635_g, chunk.field_76647_h), chunk); - this.func_73242_b(chunk); - this.field_73244_f.remove(olong); - this.field_212472_f = null; -@@ -250,6 +251,7 @@ +@@ -250,6 +250,7 @@ this.field_201723_f.func_208484_a(p_73156_1_); } diff --git a/src/main/java/net/minecraftforge/common/ForgeChunkManager.java b/src/main/java/net/minecraftforge/common/ForgeChunkManager.java deleted file mode 100644 index 04aa57a6e..000000000 --- a/src/main/java/net/minecraftforge/common/ForgeChunkManager.java +++ /dev/null @@ -1,989 +0,0 @@ -/* - * Minecraft Forge - * Copyright (c) 2016-2019. - * - * This library 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 2.1 - * of the License. - * - * This library 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 library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -package net.minecraftforge.common; - -import java.io.*; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; - -import javax.annotation.Nullable; - -import net.minecraft.nbt.NBTSizeTracker; -import net.minecraftforge.versions.forge.ForgeVersion; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.Marker; -import org.apache.logging.log4j.MarkerManager; - -import net.minecraft.entity.Entity; -import net.minecraft.nbt.CompressedStreamTools; -import net.minecraft.nbt.NBTTagCompound; -import net.minecraft.nbt.NBTTagList; -import net.minecraft.tileentity.TileEntity; -import net.minecraft.util.ClassInheritanceMultiMap; -import net.minecraft.util.math.ChunkPos; -import net.minecraft.util.math.MathHelper; -import net.minecraft.world.IWorld; -import net.minecraft.world.World; -import net.minecraft.world.WorldServer; -import net.minecraft.world.chunk.Chunk; -import net.minecraft.world.chunk.IChunk; -import net.minecraft.world.chunk.storage.AnvilChunkLoader; -import net.minecraft.world.storage.ThreadedFileIOBase; -import net.minecraftforge.common.util.Constants; -import net.minecraftforge.fml.server.ServerLifecycleHooks; -import net.minecraftforge.fml.ModContainer; -import net.minecraftforge.fml.ModList; - -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.BiMap; -import com.google.common.collect.HashBiMap; -import com.google.common.collect.HashMultimap; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableListMultimap; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.ImmutableSetMultimap; -import com.google.common.collect.LinkedHashMultimap; -import com.google.common.collect.ListMultimap; -import com.google.common.collect.MapMaker; -import com.google.common.collect.Maps; -import com.google.common.collect.Multimap; -import com.google.common.collect.SetMultimap; -import com.google.common.collect.Sets; - -/** - * Manages chunkloading for mods. - * - * The basic principle is a ticket based system. - * 1. Mods register a callback {@link #setForcedChunkLoadingCallback(Object, LoadingCallback)} - * 2. Mods ask for a ticket {@link #requestTicket(Object, World, Type)} and then hold on to that ticket. - * 3. Mods request chunks to stay loaded {@link #forceChunk(Ticket, ChunkPos)} or remove chunks from force loading {@link #unforceChunk(Ticket, ChunkPos)}. - * 4. When a world unloads, the tickets associated with that world are saved by the chunk manager. - * 5. When a world loads, saved tickets are offered to the mods associated with the tickets. The {@link Ticket#getModData()} that is set by the mod should be used to re-register - * chunks to stay loaded (and maybe take other actions). - * - * The chunkloading is configurable at runtime. The file "config/forgeChunkLoading.cfg" contains both default configuration for chunkloading, and a sample individual mod - * specific override section. - * - */ -public class ForgeChunkManager -{ - public static Marker CHUNK_MANAGER = MarkerManager.getMarker("CHUNKMANAGER"); - private static Logger LOGGER = LogManager.getLogger(); - - private static Map> tickets = new MapMaker().weakKeys().makeMap(); - - private static SetMultimap playerTickets = HashMultimap.create(); - - private static Map callbacks = Maps.newHashMap(); - - private static Map> forcedChunks = new MapMaker().weakKeys().makeMap(); - private static BiMap pendingEntities = HashBiMap.create(); - - private static Map> dormantChunkCache = new MapMaker().weakKeys().makeMap(); - - public static boolean asyncChunkLoading; - - private static Set warnedMods = Sets.newHashSet(); - - private static class ChunkEntry - { - public final Chunk chunk; - public final NBTTagCompound nbt; - - public ChunkEntry(Chunk chunk) - { - this.chunk = chunk; - this.nbt = new NBTTagCompound(); - } - } - - /** - * All mods requiring chunkloading need to implement this to handle the - * re-registration of chunk tickets at world loading time - * - * @author cpw - * - */ - public interface LoadingCallback - { - /** - * Called back when tickets are loaded from the world to allow the - * mod to re-register the chunks associated with those tickets. The list supplied - * here is truncated to length prior to use. Tickets unwanted by the - * mod must be disposed of manually unless the mod is an OrderedLoadingCallback instance - * in which case, they will have been disposed of by the earlier callback. - * - * @param tickets The tickets to re-register. The list is immutable and cannot be manipulated directly. Copy it first. - * @param world the world - */ - void ticketsLoaded(List tickets, World world); - } - - /** - * This is a special LoadingCallback that can be implemented as well as the - * LoadingCallback to provide access to additional behaviour. - * Specifically, this callback will fire prior to Forge dropping excess - * tickets. Tickets in the returned list are presumed ordered and excess will - * be truncated from the returned list. - * This allows the mod to control not only if they actually want a ticket but - * also their preferred ticket ordering. - * - * @author cpw - * - */ - public interface OrderedLoadingCallback extends LoadingCallback - { - /** - * Called back when tickets are loaded from the world to allow the - * mod to decide if it wants the ticket still, and prioritise overflow - * based on the ticket count. - * WARNING: You cannot force chunks in this callback, it is strictly for allowing the mod - * to be more selective in which tickets it wishes to preserve in an overflow situation - * - * @param tickets The tickets that you will want to select from. The list is immutable and cannot be manipulated directly. Copy it first. - * @param world The world - * @param maxTicketCount The maximum number of tickets that will be allowed. - * @return A list of the tickets this mod wishes to continue using. This list will be truncated - * to "maxTicketCount" size after the call returns and then offered to the other callback - * method - */ - List ticketsLoaded(List tickets, World world, int maxTicketCount); - } - - public interface PlayerOrderedLoadingCallback extends LoadingCallback - { - /** - * Called back when tickets are loaded from the world to allow the - * mod to decide if it wants the ticket still. - * This is for player bound tickets rather than mod bound tickets. It is here so mods can - * decide they want to dump all player tickets - * - * WARNING: You cannot force chunks in this callback, it is strictly for allowing the mod - * to be more selective in which tickets it wishes to preserve - * - * @param tickets The tickets that you will want to select from. The list is immutable and cannot be manipulated directly. Copy it first. - * @param world The world - * @return A list of the tickets this mod wishes to use. This list will subsequently be offered - * to the main callback for action - */ - ListMultimap playerTicketsLoaded(ListMultimap tickets, World world); - } - public enum Type - { - - /** - * For non-entity registrations - */ - NORMAL, - /** - * For entity registrations - */ - ENTITY - } - public static class Ticket - { - private String modId; - private Type ticketType; - private LinkedHashSet requestedChunks; - private NBTTagCompound modData; - public final World world; - private int maxDepth; - //private String entityClazz; - private int entityChunkX; - private int entityChunkZ; - private Entity entity; - private String player; - - Ticket(String modId, Type type, World world) - { - this.modId = modId; - this.ticketType = type; - this.world = world; - this.maxDepth = ForgeConfig.CHUNK.chunksPerTicket(modId); - this.requestedChunks = Sets.newLinkedHashSet(); - } - - Ticket(String modId, Type type, World world, String player) - { - this(modId, type, world); - if (player != null) - { - this.player = player; - } - else - { - LOGGER.error(CHUNK_MANAGER, "Attempt to create a player ticket without a valid player"); - throw new RuntimeException(); - } - } - /** - * The chunk list depth can be manipulated up to the maximal grant allowed for the mod. This value is configurable. Once the maximum is reached, - * the least recently forced chunk, by original registration time, is removed from the forced chunk list. - * - * @param depth The new depth to set - */ - public void setChunkListDepth(int depth) - { - - if (depth > maxDepth || (depth <= 0 && maxDepth > 0)) - { - LOGGER.warn(CHUNK_MANAGER, "The mod {} tried to modify the chunk ticket depth to: {}, its allowed maximum is: {}", modId, depth, maxDepth); - } - else - { - this.maxDepth = depth; - } - } - - /** - * Gets the current max depth for this ticket. - * Should be the same as getMaxChunkListDepth() - * unless setChunkListDepth has been called. - * - * @return Current max depth - */ - public int getChunkListDepth() - { - return maxDepth; - } - - /** - * Get the maximum chunk depth size - * - * @return The maximum chunk depth size - */ - public int getMaxChunkListDepth() - { - return maxDepth; - } - - /** - * Bind the entity to the ticket for {@link Type#ENTITY} type tickets. Other types will throw a runtime exception. - * - * @param entity The entity to bind - */ - public void bindEntity(Entity entity) - { - if (ticketType!=Type.ENTITY) - { - throw new RuntimeException("Cannot bind an entity to a non-entity ticket"); - } - this.entity = entity; - } - - /** - * Retrieve the {@link NBTTagCompound} that stores mod specific data for the chunk ticket. - * Example data to store would be a TileEntity or Block location. This is persisted with the ticket and - * provided to the {@link LoadingCallback} for the mod. It is recommended to use this to recover - * useful state information for the forced chunks. - * - * @return The custom compound tag for mods to store additional chunkloading data - */ - public NBTTagCompound getModData() - { - if (this.modData == null) - { - this.modData = new NBTTagCompound(); - } - return modData; - } - - /** - * Get the entity associated with this {@link Type#ENTITY} type ticket - * @return the entity - */ - public Entity getEntity() - { - return entity; - } - - /** - * Is this a player associated ticket rather than a mod associated ticket? - */ - public boolean isPlayerTicket() - { - return player != null; - } - - /** - * Get the player associated with this ticket - */ - public String getPlayerName() - { - return player; - } - - /** - * Get the associated mod id - */ - public String getModId() - { - return modId; - } - - /** - * Gets the ticket type - */ - public Type getType() - { - return ticketType; - } - - /** - * Gets a list of requested chunks for this ticket. - */ - public ImmutableSet getChunkList() - { - return ImmutableSet.copyOf(requestedChunks); - } - } - - public static class ForceChunkEvent extends net.minecraftforge.eventbus.api.Event - { - private final Ticket ticket; - private final ChunkPos location; - - public ForceChunkEvent(Ticket ticket, ChunkPos location) - { - this.ticket = ticket; - this.location = location; - } - - public Ticket getTicket() - { - return ticket; - } - - public ChunkPos getLocation() - { - return location; - } - } - - public static class UnforceChunkEvent extends net.minecraftforge.eventbus.api.Event - { - private final Ticket ticket; - private final ChunkPos location; - - public UnforceChunkEvent(Ticket ticket, ChunkPos location) - { - this.ticket = ticket; - this.location = location; - } - - public Ticket getTicket() - { - return ticket; - } - - public ChunkPos getLocation() - { - return location; - } - } - - - /** - * Allows dynamically loading world mods to test if there are chunk tickets in the world - * Mods that add dynamically generated worlds (like Mystcraft) should call this method - * to determine if the world should be loaded during server starting. - * - * @param chunkDir The chunk directory to test: should be equivalent to {@link WorldServer#getChunkSaveLocation()} - * @return if there are tickets outstanding for this world or not - */ - public static boolean savedWorldHasForcedChunkTickets(File chunkDir) - { - File chunkLoaderData = new File(chunkDir, "forcedchunks.dat"); - - if (chunkLoaderData.exists() && chunkLoaderData.isFile()) - { - ; - try - { - NBTTagCompound forcedChunkData = CompressedStreamTools.read(chunkLoaderData); - return forcedChunkData.getList("TicketList", Constants.NBT.TAG_COMPOUND).size() > 0; - } - catch (IOException e) - { - } - } - return false; - } - - static void loadWorld(IWorld iworld) - { - if (!(iworld instanceof World)) return; - World world = (World)iworld; - - ArrayListMultimap newTickets = ArrayListMultimap.create(); - tickets.put(world, newTickets); - - forcedChunks.put(world, ImmutableSetMultimap.of()); - - if (!(world instanceof WorldServer)) - { - return; - } - - if (ForgeConfig.CHUNK.dormantChunkCacheSize.get() != 0) - { // only put into cache if we're using dormant chunk caching - dormantChunkCache.put(world, CacheBuilder.newBuilder().maximumSize(ForgeConfig.CHUNK.dormantChunkCacheSize.get()).build()); - } - WorldServer worldServer = (WorldServer) world; - File chunkDir = worldServer.getChunkSaveLocation(); - File chunkLoaderData = new File(chunkDir, "forcedchunks.dat"); - - if (chunkLoaderData.exists() && chunkLoaderData.isFile()) - { - ArrayListMultimap loadedTickets = ArrayListMultimap.create(); - Map> playerLoadedTickets = Maps.newHashMap(); - NBTTagCompound forcedChunkData; - try (DataInputStream datainputstream = new DataInputStream(new FileInputStream(chunkLoaderData))) - { - forcedChunkData = CompressedStreamTools.read(datainputstream, NBTSizeTracker.INFINITE); - } - catch (IOException e) - { - LOGGER.warn(CHUNK_MANAGER, "Unable to read forced chunk data at {} - it will be ignored", chunkLoaderData.getAbsolutePath(), e); - return; - } - NBTTagList ticketList = forcedChunkData.getList("TicketList", Constants.NBT.TAG_COMPOUND); - for (int i = 0; i < ticketList.size(); i++) - { - NBTTagCompound ticketHolder = ticketList.getCompound(i); - String modId = ticketHolder.getString("Owner"); - boolean isPlayer = ForgeVersion.MOD_ID.equals(modId); - - if (!isPlayer && !ModList.get().isLoaded(modId)) - { - LOGGER.warn(CHUNK_MANAGER, "Found chunkloading data for mod {} which is currently not available or active - it will be removed from the world save", modId); - continue; - } - - if (!isPlayer && !callbacks.containsKey(modId)) - { - LOGGER.warn(CHUNK_MANAGER, "The mod {} has registered persistent chunkloading data but doesn't seem to want to be called back with it - it will be removed from the world save", modId); - continue; - } - - NBTTagList tickets = ticketHolder.getList("Tickets", Constants.NBT.TAG_COMPOUND); - for (int j = 0; j < tickets.size(); j++) - { - NBTTagCompound ticket = tickets.getCompound(j); - modId = ticket.hasKey("ModId") ? ticket.getString("ModId") : modId; - Type type = Type.values()[ticket.getByte("Type")]; - //byte ticketChunkDepth = ticket.getByte("ChunkListDepth"); - Ticket tick = new Ticket(modId, type, world); - if (ticket.hasKey("ModData")) - { - tick.modData = ticket.getCompound("ModData"); - } - if (ticket.hasKey("Player")) - { - tick.player = ticket.getString("Player"); - if (!playerLoadedTickets.containsKey(tick.modId)) - { - playerLoadedTickets.put(modId, ArrayListMultimap.create()); - } - playerLoadedTickets.get(tick.modId).put(tick.player, tick); - } - else - { - loadedTickets.put(modId, tick); - } - if (type == Type.ENTITY) - { - tick.entityChunkX = ticket.getInt("chunkX"); - tick.entityChunkZ = ticket.getInt("chunkZ"); - UUID uuid = new UUID(ticket.getLong("PersistentIDMSB"), ticket.getLong("PersistentIDLSB")); - // add the ticket to the "pending entity" list - pendingEntities.put(uuid, tick); - } - } - } - - for (Ticket tick : ImmutableSet.copyOf(pendingEntities.values())) - { - if (tick.ticketType == Type.ENTITY && tick.entity == null) - { - // force the world to load the entity's chunk - // the load will come back through the loadEntity method and attach the entity - // to the ticket - world.getChunk(tick.entityChunkX, tick.entityChunkZ); - } - } - for (Ticket tick : ImmutableSet.copyOf(pendingEntities.values())) - { - if (tick.ticketType == Type.ENTITY && tick.entity == null) - { - LOGGER.warn(CHUNK_MANAGER, "Failed to load persistent chunkloading entity {} from store.", pendingEntities.inverse().get(tick)); - loadedTickets.remove(tick.modId, tick); - } - } - pendingEntities.clear(); - // send callbacks - for (String modId : loadedTickets.keySet()) - { - LoadingCallback loadingCallback = callbacks.get(modId); - if (loadingCallback == null) - { - continue; - } - int maxTicketLength = ForgeConfig.CHUNK.maxTickets(modId); - List tickets = loadedTickets.get(modId); - if (loadingCallback instanceof OrderedLoadingCallback) - { - OrderedLoadingCallback orderedLoadingCallback = (OrderedLoadingCallback) loadingCallback; - tickets = orderedLoadingCallback.ticketsLoaded(ImmutableList.copyOf(tickets), world, maxTicketLength); - } - if (tickets.size() > maxTicketLength) - { - LOGGER.warn(CHUNK_MANAGER, "The mod {} has too many open chunkloading tickets {}. Excess will be dropped", modId, tickets.size()); - tickets.subList(maxTicketLength, tickets.size()).clear(); - } - ForgeChunkManager.tickets.get(world).putAll(modId, tickets); - loadingCallback.ticketsLoaded(ImmutableList.copyOf(tickets), world); - } - for (String modId : playerLoadedTickets.keySet()) - { - LoadingCallback loadingCallback = callbacks.get(modId); - if (loadingCallback == null) - { - continue; - } - ListMultimap tickets = playerLoadedTickets.get(modId); - if (loadingCallback instanceof PlayerOrderedLoadingCallback) - { - PlayerOrderedLoadingCallback orderedLoadingCallback = (PlayerOrderedLoadingCallback) loadingCallback; - tickets = orderedLoadingCallback.playerTicketsLoaded(ImmutableListMultimap.copyOf(tickets), world); - playerTickets.putAll(tickets); - } - ForgeChunkManager.tickets.get(world).putAll(ForgeVersion.MOD_ID, tickets.values()); - loadingCallback.ticketsLoaded(ImmutableList.copyOf(tickets.values()), world); - } - } - } - - static void unloadWorld(IWorld iworld) - { - if (!(iworld instanceof World)) return; - World world = (World)iworld; - // World save fires before this event so the chunk loading info will be done - if (!(world instanceof WorldServer)) - { - return; - } - - forcedChunks.remove(world); - if (ForgeConfig.CHUNK.dormantChunkCacheSize.get() != 0) // only if in use - { - dormantChunkCache.remove(world); - } - // integrated server is shutting down - if (!ServerLifecycleHooks.getCurrentServer().isServerRunning()) - { - playerTickets.clear(); - tickets.clear(); - } - } - - /** - * Set a chunkloading callback for the supplied mod object - * - * @param mod The mod instance registering the callback - * @param callback The code to call back when forced chunks are loaded - */ - public static void setForcedChunkLoadingCallback(Object mod, LoadingCallback callback) - { - ModContainer container = getContainer(mod); - if (container == null) - { - LOGGER.warn(CHUNK_MANAGER, "Unable to register a callback for an unknown mod {} ({} : {})", mod, mod.getClass().getName(), Integer.toHexString(System.identityHashCode(mod))); - return; - } - - callbacks.put(container.getModId(), callback); - } - - /** - * Discover the available tickets for the mod in the world - * - * @param mod The mod that will own the tickets - * @param world The world - * @return The count of tickets left for the mod in the supplied world - */ - public static int ticketCountAvailableFor(Object mod, World world) - { - ModContainer container = getContainer(mod); - if (container!=null) - { - String modId = container.getModId(); - int allowedCount = ForgeConfig.CHUNK.maxTickets(modId); - return allowedCount - tickets.get(world).get(modId).size(); - } - else - { - return 0; - } - } - - private static ModContainer getContainer(Object mod) - { - ModContainer container = ModList.get().getModContainerByObject(mod).orElse(null); - return container; - } - - public static int ticketCountAvailableFor(String username) - { - return ForgeConfig.CHUNK.playerTicketCount.get() - playerTickets.get(username).size(); - } - - @Nullable - public static Ticket requestPlayerTicket(Object mod, String player, World world, Type type) - { - ModContainer mc = getContainer(mod); - if (mc == null) - { - LOGGER.error(CHUNK_MANAGER, "Failed to locate the container for mod instance {} ({} : {})", mod, mod.getClass().getName(), Integer.toHexString(System.identityHashCode(mod))); - return null; - } - if (playerTickets.get(player).size() > ForgeConfig.CHUNK.playerTicketCount.get()) - { - LOGGER.warn(CHUNK_MANAGER, "Unable to assign further chunkloading tickets to player {} (on behalf of mod {})", player, mc.getModId()); - return null; - } - Ticket ticket = new Ticket(mc.getModId(),type,world,player); - playerTickets.put(player, ticket); - tickets.get(world).put(ForgeVersion.MOD_ID, ticket); - return ticket; - } - /** - * Request a chunkloading ticket of the appropriate type for the supplied mod - * - * @param mod The mod requesting a ticket - * @param world The world in which it is requesting the ticket - * @param type The type of ticket - * @return A ticket with which to register chunks for loading, or null if no further tickets are available - */ - @Nullable - public static Ticket requestTicket(Object mod, World world, Type type) - { - ModContainer container = getContainer(mod); - if (container == null) - { - LOGGER.error(CHUNK_MANAGER, "Failed to locate the container for mod instance {} ({} : {})", mod, mod.getClass().getName(), Integer.toHexString(System.identityHashCode(mod))); - return null; - } - String modId = container.getModId(); - if (!callbacks.containsKey(modId)) - { - LOGGER.fatal(CHUNK_MANAGER, "The mod {} has attempted to request a ticket without a listener in place", modId); - throw new RuntimeException("Invalid ticket request"); - } - - int allowedCount = ForgeConfig.CHUNK.maxTickets(modId); - - if (tickets.get(world).get(modId).size() >= allowedCount) - { - if (!warnedMods.contains(modId)) - { - LOGGER.info(CHUNK_MANAGER, "The mod {} has attempted to allocate a chunkloading ticket beyond it's currently allocated maximum: {}", modId, allowedCount); - warnedMods.add(modId); - } - return null; - } - Ticket ticket = new Ticket(modId, type, world); - tickets.get(world).put(modId, ticket); - - return ticket; - } - - /** - * Release the ticket back to the system. This will also unforce any chunks held by the ticket so that they can be unloaded and/or stop ticking. - * - * @param ticket The ticket to release - */ - public static void releaseTicket(Ticket ticket) - { - if (ticket == null) - { - return; - } - if (ticket.isPlayerTicket() ? !playerTickets.containsValue(ticket) : !tickets.get(ticket.world).containsEntry(ticket.modId, ticket)) - { - return; - } - if (ticket.requestedChunks!=null) - { - for (ChunkPos chunk : ImmutableSet.copyOf(ticket.requestedChunks)) - { - unforceChunk(ticket, chunk); - } - } - if (ticket.isPlayerTicket()) - { - playerTickets.remove(ticket.player, ticket); - tickets.get(ticket.world).remove(ForgeVersion.MOD_ID, ticket); - } - else - { - tickets.get(ticket.world).remove(ticket.modId, ticket); - } - } - - /** - * Force the supplied chunk coordinate to be loaded by the supplied ticket. If the ticket's {@link Ticket#maxDepth} is exceeded, the least - * recently registered chunk is unforced and may be unloaded. - * It is safe to force the chunk several times for a ticket, it will not generate duplication or change the ordering. - * - * @param ticket The ticket registering the chunk - * @param chunk The chunk to force - */ - public static void forceChunk(Ticket ticket, ChunkPos chunk) - { - if (ticket == null || chunk == null) - { - return; - } - if (ticket.ticketType == Type.ENTITY && ticket.entity == null) - { - throw new RuntimeException("Attempted to use an entity ticket to force a chunk, without an entity"); - } - if (ticket.isPlayerTicket() ? !playerTickets.containsValue(ticket) : !tickets.get(ticket.world).containsEntry(ticket.modId, ticket)) - { - LOGGER.fatal(CHUNK_MANAGER, "The mod {} attempted to force load a chunk with an invalid ticket. This is not permitted.", ticket.modId); - return; - } - ticket.requestedChunks.add(chunk); - MinecraftForge.EVENT_BUS.post(new ForceChunkEvent(ticket, chunk)); - - ImmutableSetMultimap newMap = ImmutableSetMultimap.builder().putAll(forcedChunks.get(ticket.world)).put(chunk, ticket).build(); - forcedChunks.put(ticket.world, newMap); - if (ticket.maxDepth > 0 && ticket.requestedChunks.size() > ticket.maxDepth) - { - ChunkPos removed = ticket.requestedChunks.iterator().next(); - unforceChunk(ticket,removed); - } - } - - /** - * Reorganize the internal chunk list so that the chunk supplied is at the *end* of the list - * This helps if you wish to guarantee a certain "automatic unload ordering" for the chunks - * in the ticket list - * - * @param ticket The ticket holding the chunk list - * @param chunk The chunk you wish to push to the end (so that it would be unloaded last) - */ - public static void reorderChunk(Ticket ticket, ChunkPos chunk) - { - if (ticket == null || chunk == null || !ticket.requestedChunks.contains(chunk)) - { - return; - } - ticket.requestedChunks.remove(chunk); - ticket.requestedChunks.add(chunk); - } - /** - * Unforce the supplied chunk, allowing it to be unloaded and stop ticking. - * - * @param ticket The ticket holding the chunk - * @param chunk The chunk to unforce - */ - public static void unforceChunk(Ticket ticket, ChunkPos chunk) - { - if (ticket == null || chunk == null) - { - return; - } - ticket.requestedChunks.remove(chunk); - MinecraftForge.EVENT_BUS.post(new UnforceChunkEvent(ticket, chunk)); - LinkedHashMultimap copy = LinkedHashMultimap.create(forcedChunks.get(ticket.world)); - copy.remove(chunk, ticket); - ImmutableSetMultimap newMap = ImmutableSetMultimap.copyOf(copy); - forcedChunks.put(ticket.world,newMap); - } - - /** - * The list of persistent chunks in the world. This set is immutable. - * @param world - * @return the list of persistent chunks in the world - */ - public static ImmutableSetMultimap getPersistentChunksFor(World world) - { - return forcedChunks.containsKey(world) ? forcedChunks.get(world) : ImmutableSetMultimap.of(); - } - - static void saveWorld(IWorld iworld) - { - if (!(iworld instanceof World)) return; - World world = (World)iworld; - // only persist persistent worlds - if (!(world instanceof WorldServer)) - { - return; - } - WorldServer worldServer = (WorldServer) world; - File chunkDir = worldServer.getChunkSaveLocation(); - File chunkLoaderData = new File(chunkDir, "forcedchunks.dat"); - - NBTTagCompound forcedChunkData = new NBTTagCompound(); - NBTTagList ticketList = new NBTTagList(); - forcedChunkData.setTag("TicketList", ticketList); - - Multimap ticketSet = tickets.get(worldServer); - if (ticketSet == null) return; - for (String modId : ticketSet.keySet()) - { - NBTTagCompound ticketHolder = new NBTTagCompound(); - ticketList.add(ticketHolder); - - ticketHolder.setString("Owner", modId); - NBTTagList tickets = new NBTTagList(); - ticketHolder.setTag("Tickets", tickets); - - for (Ticket tick : ticketSet.get(modId)) - { - NBTTagCompound ticket = new NBTTagCompound(); - ticket.setByte("Type", (byte) tick.ticketType.ordinal()); - ticket.setByte("ChunkListDepth", (byte) tick.maxDepth); - if (tick.isPlayerTicket()) - { - ticket.setString("ModId", tick.modId); - ticket.setString("Player", tick.player); - } - if (tick.modData != null) - { - ticket.setTag("ModData", tick.modData); - } - if (tick.ticketType == Type.ENTITY && tick.entity != null && tick.entity.writeUnlessPassenger(new NBTTagCompound())) - { - ticket.setInt("chunkX", MathHelper.floor(tick.entity.chunkCoordX)); - ticket.setInt("chunkZ", MathHelper.floor(tick.entity.chunkCoordZ)); - ticket.setLong("PersistentIDMSB", tick.entity.getUniqueID().getMostSignificantBits()); - ticket.setLong("PersistentIDLSB", tick.entity.getUniqueID().getLeastSignificantBits()); - tickets.add(ticket); - } - else if (tick.ticketType != Type.ENTITY) - { - tickets.add(ticket); - } - } - } - - // Write the actual file on the IO thread rather than blocking the server thread - ThreadedFileIOBase.getThreadedIOInstance().queueIO(() -> { - try (DataOutputStream dataoutputstream = new DataOutputStream(new FileOutputStream(chunkLoaderData))) { - CompressedStreamTools.write(forcedChunkData, dataoutputstream); - } - catch (IOException e) - { - LOGGER.warn(CHUNK_MANAGER, "Unable to write forced chunk data to {} - chunkloading won't work", chunkLoaderData.getAbsolutePath(), e); - } - return false; - }); - } - - static void loadEntity(Entity entity) - { - UUID id = entity.getUniqueID(); - Ticket tick = pendingEntities.get(id); - if (tick != null) - { - tick.bindEntity(entity); - pendingEntities.remove(id); - } - } - - public static void putDormantChunk(long coords, Chunk chunk) - { - if (ForgeConfig.CHUNK.dormantChunkCacheSize.get() == 0) return; // Skip if we're not dormant caching chunks - Cache cache = dormantChunkCache.get(chunk.getWorld()); - if (cache != null) - { - cache.put(coords, new ChunkEntry(chunk)); - } - } - - public static void storeChunkNBT(World world, IChunk ichunk, NBTTagCompound nbt) - { - if (ForgeConfig.CHUNK.dormantChunkCacheSize.get() == 0) return; - - Cache cache = dormantChunkCache.get(world); - if (cache == null) return; - - ChunkEntry entry = cache.getIfPresent(ichunk.getPos().asLong()); - if (entry != null) - { - entry.nbt.setTag("Entities", nbt.getList("Entities", Constants.NBT.TAG_COMPOUND)); - entry.nbt.setTag("TileEntities", nbt.getList("TileEntities", Constants.NBT.TAG_COMPOUND)); - - if (ichunk instanceof Chunk) - { - Chunk chunk = (Chunk)ichunk; - ClassInheritanceMultiMap[] entityLists = chunk.getEntityLists(); - for (int i = 0; i < entityLists.length; ++i) - { - entityLists[i] = new ClassInheritanceMultiMap<>(Entity.class); - } - chunk.getTileEntityMap().clear(); - } - } - } - - @Nullable - public static Chunk fetchDormantChunk(long coords, World world) - { - if (ForgeConfig.CHUNK.dormantChunkCacheSize.get() == 0) return null; // Don't bother with maps at all if its never gonna get a response - - Cache cache = dormantChunkCache.get(world); - if (cache == null) return null; - - ChunkEntry entry = cache.getIfPresent(coords); - if (entry == null) return null; - - loadChunkEntities(entry.chunk, entry.nbt, world); - - cache.invalidate(coords); - return entry.chunk; - } - - private static void loadChunkEntities(Chunk chunk, NBTTagCompound nbt, World world) - { - NBTTagList entities = nbt.getList("Entities", Constants.NBT.TAG_COMPOUND); - for (int i = 0; i < entities.size(); ++i) - { - AnvilChunkLoader.readChunkEntity(entities.getCompound(i), world, chunk); - chunk.setHasEntities(true); - } - - NBTTagList tileEntities = nbt.getList("TileEntities", Constants.NBT.TAG_COMPOUND); - for (int i = 0; i < tileEntities.size(); ++i) - { - TileEntity tileEntity = TileEntity.create(tileEntities.getCompound(i)); - if (tileEntity != null) chunk.addTileEntity(tileEntity); - } - } -} diff --git a/src/main/java/net/minecraftforge/common/ForgeConfig.java b/src/main/java/net/minecraftforge/common/ForgeConfig.java index e1e4b5344..5e58eab59 100644 --- a/src/main/java/net/minecraftforge/common/ForgeConfig.java +++ b/src/main/java/net/minecraftforge/common/ForgeConfig.java @@ -186,103 +186,6 @@ public class ForgeConfig SERVER = specPair.getLeft(); } - - - private static final ForgeConfigSpec.Builder CHUNK_BUILDER = new ForgeConfigSpec.Builder(); - - public static final Chunk CHUNK = new Chunk(CHUNK_BUILDER); - - public static class Chunk { - - public final BooleanValue enable; - - public final IntValue chunksPerTicket; - - public final IntValue maxTickets; - - public final IntValue playerTicketCount; - - public final IntValue dormantChunkCacheSize; - - public final BooleanValue asyncChunkLoading; - - private final ForgeConfigSpec chunkSpec = new ForgeConfigSpec.Builder() - .define("modid", "forge").next() - .defineInRange("maxTickets", 200, 0, Integer.MAX_VALUE).next() - .defineInRange("chunksPerTicket", 25, 0, Integer.MAX_VALUE).next() - .build(); - - private final CommentedConfig modCfgDefault = CommentedConfig.inMemory(); - - - Chunk(ForgeConfigSpec.Builder builder) { - builder.comment("Default configuration for Forge chunk loading control") - .push("defaults"); - - enable = builder - .comment("Allow mod overrides, false will use default for everything.") - .translation("forge.configgui.enableModOverrides") - .define("enable", true); - - chunksPerTicket = builder - .comment("The default maximum number of chunks a mod can force, per ticket,", - "for a mod without an override. This is the maximum number of chunks a single ticket can force.") - .translation("forge.configgui.maximumChunksPerTicket") - .defineInRange("chunksPerTicket", 25, 0, Integer.MAX_VALUE); - - maxTickets = builder - .comment("The default maximum ticket count for a mod which does not have an override", - "in this file. This is the number of chunk loading requests a mod is allowed to make.") - .translation("forge.configgui.maximumTicketCount") - .defineInRange("maxTickets", 200, 0, Integer.MAX_VALUE); - - playerTicketCount = builder - .comment("The number of tickets a player can be assigned instead of a mod. This is shared across all mods and it is up to the mods to use it.") - .translation("forge.configgui.playerTicketCount") - .defineInRange("playerTicketCount", 500, 0, Integer.MAX_VALUE); - - dormantChunkCacheSize = builder - .comment("Unloaded chunks can first be kept in a dormant cache for quicker loading times. Specify the size (in chunks) of that cache here") - .translation("forge.configgui.dormantChunkCacheSize") - .defineInRange("dormantChunkCacheSize", 0, 0, Integer.MAX_VALUE); - - asyncChunkLoading = builder - .comment("Load chunks asynchronously for players, reducing load on the server thread.", - "Can be disabled to help troubleshoot chunk loading issues.") - .translation("forge.configgui.asyncChunkLoading") - .define("asyncChunkLoading", true); - - chunkSpec.correct(modCfgDefault); - builder.pop(); - } - - private final ForgeConfigSpec.ConfigValue> mods = CHUNK_BUILDER - .defineList("mods", Lists.newArrayList(modCfgDefault), o -> { - if (!(o instanceof CommentedConfig)) return false; - return chunkSpec.isCorrect((CommentedConfig) o); - }); - - private int getByMod(ForgeConfigSpec.ConfigValue def, String name, String modid) { - if (!enable.get() || modid == null) - return def.get(); - - return mods.get().stream().filter(c -> modid.equals(c.get("modid"))).findFirst() - .map(c -> c.get(name)) - .orElseGet(def::get); - } - - public int maxTickets(@Nullable String modid) { - return getByMod(maxTickets, "maxTickets", modid); - } - - public int chunksPerTicket(@Nullable String modid) { - return getByMod(chunksPerTicket, "chunksPerTicket", modid); - } - } - - public static final ForgeConfigSpec chunk_spec = CHUNK_BUILDER.build(); - - @SubscribeEvent public static void onLoad(final ModConfig.Loading configEvent) { LogManager.getLogger().debug(FORGEMOD, "Loaded forge config file {}", configEvent.getConfig().getFileName()); diff --git a/src/main/java/net/minecraftforge/common/ForgeInternalHandler.java b/src/main/java/net/minecraftforge/common/ForgeInternalHandler.java index 0440dc6f8..c768b6892 100644 --- a/src/main/java/net/minecraftforge/common/ForgeInternalHandler.java +++ b/src/main/java/net/minecraftforge/common/ForgeInternalHandler.java @@ -40,11 +40,6 @@ public class ForgeInternalHandler @SubscribeEvent(priority = EventPriority.HIGHEST) public void onEntityJoinWorld(EntityJoinWorldEvent event) { - if (!event.getWorld().isRemote) - { - ForgeChunkManager.loadEntity(event.getEntity()); - } - Entity entity = event.getEntity(); if (entity.getClass().equals(EntityItem.class)) { @@ -65,22 +60,10 @@ public class ForgeInternalHandler } } - @net.minecraftforge.eventbus.api.SubscribeEvent(priority = net.minecraftforge.eventbus.api.EventPriority.HIGHEST) - public void onDimensionLoad(WorldEvent.Load event) - { - ForgeChunkManager.loadWorld(event.getWorld()); - } @SubscribeEvent(priority = EventPriority.HIGHEST) - public void onDimensionSave(WorldEvent.Save event) - { - ForgeChunkManager.saveWorld(event.getWorld()); - } - - @net.minecraftforge.eventbus.api.SubscribeEvent(priority = EventPriority.HIGHEST) public void onDimensionUnload(WorldEvent.Unload event) { - ForgeChunkManager.unloadWorld(event.getWorld()); if (event.getWorld() instanceof WorldServer) FakePlayerFactory.unloadWorld((WorldServer) event.getWorld()); } diff --git a/src/main/java/net/minecraftforge/common/ForgeMod.java b/src/main/java/net/minecraftforge/common/ForgeMod.java index 9e38d5717..ab6cc4c10 100644 --- a/src/main/java/net/minecraftforge/common/ForgeMod.java +++ b/src/main/java/net/minecraftforge/common/ForgeMod.java @@ -98,20 +98,6 @@ public class ForgeMod implements WorldPersistenceHooks.WorldPersistenceHook ModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, ForgeConfig.clientSpec); ModLoadingContext.get().registerConfig(ModConfig.Type.SERVER, ForgeConfig.serverSpec); modEventBus.register(ForgeConfig.class); - loadConfig(ForgeConfig.chunk_spec, FMLPaths.CONFIGDIR.get().resolve("forge_chunks.toml")); - } - - private void loadConfig(ForgeConfigSpec spec, Path path) { - LOGGER.debug(FORGEMOD, "Loading config file {}", path); - final CommentedFileConfig configData = CommentedFileConfig.builder(path) - .sync() - .autosave() - .writingMode(WritingMode.REPLACE) - .build(); - LOGGER.debug(FORGEMOD, "Built TOML config for {}", path.toString()); - configData.load(); - LOGGER.debug(FORGEMOD, "Loaded TOML config file {}", path.toString()); - spec.setConfig(configData); } /*