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.Chunk; 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 class DataRegistry { * 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.HashMap; 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 @@ public class DataRegistryImpl { 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 class DataRegistryImpl { 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 class DataRegistryImpl { 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 class DataRegistryImpl { 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 class DataRegistryImpl { 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 class DataRegistryImpl { } 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 @@ public class DataRegistryImpl { 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 @@ public class DataRegistryImpl { } 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 class DataRegistryImpl { 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 @@ public class DataRegistryImpl { 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(); + } +}