diff --git a/build.gradle.kts b/build.gradle.kts index f03cb43..d9849f6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("com.falsepattern.fpgradle-mc") version("0.18.0") + id("com.falsepattern.fpgradle-mc") version("2.1.0") } group = "com.falsepattern" diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 1b33c55..8bdaf60 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ff23a68..2e11132 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 23d15a9..adff685 100644 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -114,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -172,7 +171,6 @@ fi # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) @@ -212,7 +210,6 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" diff --git a/gradlew.bat b/gradlew.bat index db3a6ac..c4bdd3a 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -70,11 +70,10 @@ goto fail :execute @rem Setup the command line -set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/src/main/java/com/falsepattern/chunk/api/DataRegistry.java b/src/main/java/com/falsepattern/chunk/api/DataRegistry.java index e4a6e8a..3527348 100644 --- a/src/main/java/com/falsepattern/chunk/api/DataRegistry.java +++ b/src/main/java/com/falsepattern/chunk/api/DataRegistry.java @@ -31,6 +31,7 @@ import net.minecraft.world.chunk.storage.ExtendedBlockStorage; import java.util.Set; +import java.util.SortedSet; /** * This is an API class covered by the additional permissions in the license. @@ -48,12 +49,36 @@ public class DataRegistry { * Registers a ChunkDataManager. Only do this during the init phase. * * @param manager The manager to register. + * @param ordering The natural ordering index for the data manager when iterating the list of managers. + *

+ * ChunkAPI, Lumi, RPLE, and EndlessIDs all use 0. Negative numbers are sorted earlier. Positive numbers are sorted later. + *

+ * Use 0 unless you specifically need to do something later. + *

+ * As a convention, do this in increments of 1000 so that other people can order "between" your manager and the base managers. + * * * @throws IllegalStateException If the registration stage is over. * @throws IllegalArgumentException If the manager has a duplicate id. */ + public static void registerDataManager(DataManager manager, int ordering) throws IllegalStateException, IllegalArgumentException { + DataRegistryImpl.registerDataManager(manager, ordering); + } + + /** + * Registers a ChunkDataManager. Only do this during the init phase. + * Has an implicit ordering index of 0. + * + * @deprecated Use {@link #registerDataManager(DataManager, int)} with an explicit ordering. + * + * @param manager The manager to register. + * + * @throws IllegalStateException If the registration stage is over. + * @throws IllegalArgumentException If the manager has a duplicate id. + */ + @Deprecated public static void registerDataManager(DataManager manager) throws IllegalStateException, IllegalArgumentException { - DataRegistryImpl.registerDataManager(manager); + DataRegistryImpl.registerDataManager(manager, 0); } /** @@ -72,10 +97,22 @@ public static void disableDataManager(String domain, String id) throws IllegalSt * The id of a manager is its domain and id separated by a colon. (domain:id) */ @Contract(pure = true) + @Deprecated public static @Unmodifiable Set getRegisteredManagers() { return DataRegistryImpl.getRegisteredManagers(); } + /** + * Returns an unmodifiable set of all registered ChunkDataManagers. + * The id of a manager is its domain and id separated by a colon. (domain:id) + *

+ * This function also includes the ordering index of each manager. + */ + @Contract(pure = true) + public static @Unmodifiable SortedSet getRegisteredManagersOrdered() { + return DataRegistryImpl.getRegisteredManagersOrdered(); + } + /** * Copies chunk-level data from a source chunk to a target chunk. * DOES NOT copy data contained inside its ExtendedBlockStorage instances!! diff --git a/src/main/java/com/falsepattern/chunk/api/OrderedManager.java b/src/main/java/com/falsepattern/chunk/api/OrderedManager.java new file mode 100644 index 0000000..6da7fc0 --- /dev/null +++ b/src/main/java/com/falsepattern/chunk/api/OrderedManager.java @@ -0,0 +1,55 @@ +/* + * ChunkAPI + * + * Copyright (C) 2023-2025 FalsePattern, The MEGA Team, LegacyModdingMC contributors + * All Rights Reserved + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * 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, only version 3 of the License. + * + * 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.falsepattern.chunk.api; + +import lombok.RequiredArgsConstructor; +import lombok.val; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +@RequiredArgsConstructor +public class OrderedManager implements Comparable { + public final int ordering; + public final @NotNull String id; + @Override + public int compareTo(OrderedManager o) { + int ord = Integer.compare(ordering, o.ordering); + if (ord != 0) + return ord; + return id.compareTo(o.id); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof OrderedManager)) + return false; + val other = (OrderedManager) obj; + return id.equals(other.id) && ordering == other.ordering; + } + + @Override + public int hashCode() { + return Objects.hash(ordering, id); + } +} diff --git a/src/main/java/com/falsepattern/chunk/internal/ChunkAPI.java b/src/main/java/com/falsepattern/chunk/internal/ChunkAPI.java index e29f818..81393d6 100644 --- a/src/main/java/com/falsepattern/chunk/internal/ChunkAPI.java +++ b/src/main/java/com/falsepattern/chunk/internal/ChunkAPI.java @@ -41,11 +41,11 @@ public class ChunkAPI { @Mod.EventHandler public void init(FMLInitializationEvent event) { - DataRegistry.registerDataManager(new BlockIDManager()); - DataRegistry.registerDataManager(new MetadataManager()); - DataRegistry.registerDataManager(new LightingManager()); - DataRegistry.registerDataManager(new BlocklightManager()); - DataRegistry.registerDataManager(new SkylightManager()); - DataRegistry.registerDataManager(new BiomeManager()); + DataRegistry.registerDataManager(new BlockIDManager(), 0); + DataRegistry.registerDataManager(new MetadataManager(), 0); + DataRegistry.registerDataManager(new LightingManager(), 0); + DataRegistry.registerDataManager(new BlocklightManager(), 0); + DataRegistry.registerDataManager(new SkylightManager(), 0); + DataRegistry.registerDataManager(new BiomeManager(), 0); } } \ No newline at end of file diff --git a/src/main/java/com/falsepattern/chunk/internal/DataRegistryImpl.java b/src/main/java/com/falsepattern/chunk/internal/DataRegistryImpl.java index 5b6645a..936bfcc 100644 --- a/src/main/java/com/falsepattern/chunk/internal/DataRegistryImpl.java +++ b/src/main/java/com/falsepattern/chunk/internal/DataRegistryImpl.java @@ -23,6 +23,7 @@ package com.falsepattern.chunk.internal; import com.falsepattern.chunk.api.DataManager; +import com.falsepattern.chunk.api.OrderedManager; import lombok.Data; import lombok.val; import lombok.var; @@ -48,15 +49,20 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.TreeSet; import java.util.stream.Collectors; public class DataRegistryImpl { - private static final Set managers = new HashSet<>(); - private static final Map packetManagers = new HashMap<>(); - private static final Map blockPacketManagers = new HashMap<>(); - private static final Map NBTManagers = new HashMap<>(); - private static final Map chunkNBTManagers = new HashMap<>(); - private static final Map subChunkNBTManagers = new HashMap<>(); + private static final Map managersUnordered = new HashMap<>(); + private static final SortedSet managers = new TreeSet<>(); + private static final DualMap packetManagers = new DualMap<>(); + private static final DualMap blockPacketManagers = new DualMap<>(); + private static final DualMap NBTManagers = new DualMap<>(); + private static final SortedMap chunkNBTManagers = new TreeMap<>(); + private static final SortedMap subChunkNBTManagers = new TreeMap<>(); private static final Set disabledManagers = new HashSet<>(); private static int maxPacketSize = 4; @@ -66,12 +72,12 @@ private static class PacketManagerInfo { public final DataManager.PacketDataManager manager; } - public static void registerDataManager(DataManager manager) throws IllegalStateException, IllegalArgumentException { + public static void registerDataManager(DataManager manager, int ordering) throws IllegalStateException, IllegalArgumentException { if (Loader.instance().getLoaderState() != LoaderState.INITIALIZATION) { throw new IllegalStateException("ChunkDataManager registration is not allowed at this time! Please register your ChunkDataManager in the init phase."); } var id = manager.domain() + ":" + manager.id(); - if (managers.contains(id)) { + if (managersUnordered.containsKey(id)) { throw new IllegalArgumentException("ChunkDataManager " + manager + " has a duplicate id!"); } @@ -80,24 +86,27 @@ public static void registerDataManager(DataManager manager) throws IllegalStateE return; } - managers.add(id); + val ord = new OrderedManager(ordering, id); + managersUnordered.put(id, ord); + managers.add(ord); if (manager instanceof DataManager.PacketDataManager) { val packetManager = (DataManager.PacketDataManager) manager; val maxSize = packetManager.maxPacketSize(); maxPacketSize += 4 + id.getBytes(StandardCharsets.UTF_8).length + 4 + maxSize; - packetManagers.put(id, new PacketManagerInfo(maxSize, packetManager)); + val man = new PacketManagerInfo(maxSize, packetManager); + packetManagers.put(ord, man); } if (manager instanceof DataManager.BlockPacketDataManager) { val blockPacketManager = (DataManager.BlockPacketDataManager) manager; - blockPacketManagers.put(id, blockPacketManager); + blockPacketManagers.put(ord, blockPacketManager); } if (manager instanceof DataManager.StorageDataManager) { - NBTManagers.put(id, (DataManager.StorageDataManager) manager); + NBTManagers.put(ord, (DataManager.StorageDataManager) manager); if (manager instanceof DataManager.ChunkDataManager) { - chunkNBTManagers.put(id, (DataManager.ChunkDataManager) manager); + chunkNBTManagers.put(ord, (DataManager.ChunkDataManager) manager); } if (manager instanceof DataManager.SubChunkDataManager) { - subChunkNBTManagers.put(id, (DataManager.SubChunkDataManager) manager); + subChunkNBTManagers.put(ord, (DataManager.SubChunkDataManager) manager); } } } @@ -109,17 +118,19 @@ public static void disableDataManager(String domain, String id) { Common.LOG.debug("Disabling ChunkDataManager " + id + " in domain " + domain + ". See the stacktrace for the source of this event.\nThis is NOT an error.", new Throwable()); val manager = domain + ":" + id; + val ord = managersUnordered.remove(manager); //Remove the manager from the list of managers, if it exists - if (managers.remove(manager)) { + if (ord != null) { + managers.remove(ord); //Clear the maps - if (packetManagers.containsKey(manager)) { - val removed = packetManagers.remove(manager); + if (packetManagers.containsKey(ord)) { + val removed = packetManagers.remove(ord); maxPacketSize -= 4 + id.getBytes(StandardCharsets.UTF_8).length + 4 + removed.maxPacketSize; } - blockPacketManagers.remove(manager); - chunkNBTManagers.remove(manager); - subChunkNBTManagers.remove(manager); - NBTManagers.remove(manager); + blockPacketManagers.remove(ord); + chunkNBTManagers.remove(ord); + subChunkNBTManagers.remove(ord); + NBTManagers.remove(ord); } //Add the manager to the list of disabled managers, in case it gets registered after this disable call. @@ -171,9 +182,9 @@ public static int writeToBuffer(Chunk chunk, int subChunkMask, boolean forceUpda buf.order(ByteOrder.LITTLE_ENDIAN); buf.putInt(packetManagers.size()); for (val pair : packetManagers.entrySet()) { - val id = pair.getKey(); + val ord = pair.getKey(); val managerInfo = pair.getValue(); - writeString(buf, id); + writeString(buf, ord.id); int start = buf.position() + 4; val slice = createSlice(buf, start, managerInfo.maxPacketSize); managerInfo.manager.writeToBuffer(chunk, subChunkMask, forceUpdate, slice); @@ -199,9 +210,9 @@ public static void readBlockFromPacket(Chunk chunk, int x, int y, int z, S23Pack public static void writeBlockPacketToBuffer(S23PacketBlockChange packet, PacketBuffer buffer) throws IOException { buffer.writeInt(blockPacketManagers.size()); for (val pair : blockPacketManagers.entrySet()) { - val id = pair.getKey(); + val ord = pair.getKey(); val manager = pair.getValue(); - buffer.writeStringToBuffer(id); + buffer.writeStringToBuffer(ord.id); manager.writeBlockPacketToBuffer(packet, buffer); } } @@ -300,7 +311,11 @@ public static void cloneSubChunk(Chunk fromChunk, ExtendedBlockStorage from, Ext } public static Set getRegisteredManagers() { - return Collections.unmodifiableSet(managers); + return Collections.unmodifiableSet(managersUnordered.keySet()); + } + + public static SortedSet getRegisteredManagersOrdered() { + return Collections.unmodifiableSortedSet(managers); } public static void readLevelDat(NBTTagCompound tag) { @@ -349,7 +364,9 @@ private static String verifyManagerCompatibility(NBTTagCompound tag) { StringBuilder builder = new StringBuilder(); val saveManagers = readManagers(tag); val removedManagers = new HashSet<>(saveManagers.keySet()); - removedManagers.removeAll(NBTManagers.keySet()); + for (val nbtManager: NBTManagers.keySet()) { + removedManagers.remove(nbtManager.id); + } if (!removedManagers.isEmpty()) { compatWarning = true; builder.append("\nThe following data managers are no longer present:\n"); @@ -370,8 +387,8 @@ private static String verifyManagerCompatibility(NBTTagCompound tag) { } val addedManagers = NBTManagers.keySet() .stream() - .filter(manager -> !manager.startsWith("minecraft:")) - .filter(manager -> !saveManagers.containsKey(manager)) + .filter(manager -> !manager.id.startsWith("minecraft:")) + .filter(manager -> !saveManagers.containsKey(manager.id)) .collect(Collectors.toSet()); if (!addedManagers.isEmpty()) { compatWarning = true; @@ -417,12 +434,12 @@ public static void writeLevelDat(NBTTagCompound tag) { tag.setTag("managers", managers); tag.setString("version", Tags.MOD_VERSION); for (val manager : NBTManagers.entrySet()) { - val name = manager.getKey(); - if (name.startsWith("minecraft:")) { + val ord = manager.getKey(); + if (ord.id.startsWith("minecraft:")) { continue; } val value = manager.getValue(); - managers.setTag(name, SaveManagerInfo.fromManager(value).toNBT()); + managers.setTag(ord.id, SaveManagerInfo.fromManager(value).toNBT()); } } @@ -435,7 +452,6 @@ private static Map readManagers(NBTTagCompound tag) { return managers; } val managerTag = tag.getCompoundTag("managers"); - //noinspection unchecked for (val key : managerTag.func_150296_c()) { managers.put(key, SaveManagerInfo.fromNBT(managerTag.getCompoundTag(key))); } diff --git a/src/main/java/com/falsepattern/chunk/internal/DualMap.java b/src/main/java/com/falsepattern/chunk/internal/DualMap.java new file mode 100644 index 0000000..16cd4cb --- /dev/null +++ b/src/main/java/com/falsepattern/chunk/internal/DualMap.java @@ -0,0 +1,86 @@ +/* + * ChunkAPI + * + * Copyright (C) 2023-2025 FalsePattern, The MEGA Team, LegacyModdingMC contributors + * All Rights Reserved + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * 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, only version 3 of the License. + * + * 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.falsepattern.chunk.internal; + +import com.falsepattern.chunk.api.OrderedManager; +import lombok.val; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; + +public class DualMap { + private final Map unordered = new HashMap<>(); + private final SortedMap ordered = new TreeMap<>(); + + public void put(OrderedManager man, T value) { + val a = unordered.put(man.id, value); + val b = ordered.put(man, value); + if (a != b) { + throw new AssertionError(); + } + } + + public int size() { + return unordered.size(); + } + + public T get(OrderedManager ord) { + return unordered.get(ord.id); + } + + public T get(String id) { + return unordered.get(id); + } + + public T remove(OrderedManager ord) { + val a = unordered.remove(ord.id); + val b = ordered.remove(ord); + if (a != b) { + throw new AssertionError(); + } + return b; + } + + public boolean containsKey(String id) { + return unordered.containsKey(id); + } + + public boolean containsKey(OrderedManager ord) { + return unordered.containsKey(ord.id); + } + + public Set keySet() { + return ordered.keySet(); + } + + public Set> entrySet() { + return ordered.entrySet(); + } + + public Iterable values() { + return ordered.values(); + } +}