Skip to content

Commit

Permalink
Fix failing to load QIO data if some items now are equivalent
Browse files Browse the repository at this point in the history
  • Loading branch information
pupnewfster committed Aug 23, 2024
1 parent eb5c3a5 commit 84e41fb
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 17 deletions.
2 changes: 1 addition & 1 deletion src/api/java/mekanism/api/MekanismAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ private MekanismAPI() {
/**
* The version of the api classes - may not always match the mod's version
*/
public static final String API_VERSION = "10.7.1";
public static final String API_VERSION = "10.7.4";
/**
* Mekanism's Mod ID
*/
Expand Down
2 changes: 1 addition & 1 deletion src/api/java/mekanism/api/MekanismAPITags.java
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ private Chemicals() {
/**
* Chemicals in this tag cannot be inserted into framed blocks
*
* @since 10.7.1
* @since 10.7.3
*/
public static final TagKey<Chemical> FRAMEDBLOCKS_BLACKLISTED = tag("framedblocks_blacklisted");

Expand Down
4 changes: 4 additions & 0 deletions src/api/java/mekanism/api/SerializationConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,10 @@ private SerializationConstants() {
public static final String ADVANCEMENT = "advancement";
public static final String ALLOW_DEFAULT = "allow_default";
public static final String AGE = "age";
/**
* @since 10.7.4
*/
public static final String ALIASES = "aliases";
public static final String ASSEMBLIES = "assemblies";
public static final String AUTO = "auto";
public static final String BABY_TYPE = "baby_type";
Expand Down
16 changes: 15 additions & 1 deletion src/main/java/mekanism/common/attachments/qio/DriveContents.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import it.unimi.dsi.fastutil.objects.Object2LongSortedMap;
import it.unimi.dsi.fastutil.objects.Object2LongSortedMaps;
import java.util.UUID;
import java.util.function.LongBinaryOperator;
import java.util.stream.LongStream;
import mekanism.api.annotations.NothingNullByDefault;
import mekanism.common.content.qio.QIODriveData;
Expand All @@ -20,6 +21,7 @@
public record DriveContents(Object2LongSortedMap<UUID> namedItemMap) {

public static final DriveContents EMPTY = new DriveContents(Object2LongSortedMaps.emptyMap());
private static final LongBinaryOperator SUM = Long::sum;

public static final Codec<DriveContents> CODEC = Codec.LONG_STREAM.xmap(
stream -> readSerializedItemMap(stream.toArray()),
Expand Down Expand Up @@ -80,8 +82,20 @@ private static DriveContents readSerializedItemMap(long[] serializedItemMap) {
if (serializedItemMap.length > 0 && serializedItemMap.length % 3 == 0) {
//Ensure we have valid data and not some value we don't know how to process
Object2LongSortedMap<UUID> namedItemMap = new Object2LongLinkedOpenHashMap<>();
boolean hasAliases = QIOGlobalItemLookup.INSTANCE.hasAliases();
for (int i = 0; i < serializedItemMap.length; i++) {
namedItemMap.put(new UUID(serializedItemMap[i++], serializedItemMap[i++]), serializedItemMap[i]);
UUID savedUUID = new UUID(serializedItemMap[i++], serializedItemMap[i++]);
long storedCount = serializedItemMap[i];
if (!hasAliases) {
//If we have no aliases stored in the lookup, we can just short circuit to directly adding the id
namedItemMap.put(savedUUID, storedCount);
} else {
//Note: getWinningId, will return the passed in id if there isn't an id to remap it to
UUID winningId = QIOGlobalItemLookup.INSTANCE.getWinningId(savedUUID);
//We merge regardless of if our item had an alias or not, so that we don't fail on the
// case where we load the alias version first, and then the one that is new might override it
namedItemMap.mergeLong(winningId, storedCount, SUM);
}
}
return new DriveContents(namedItemMap);
}
Expand Down
89 changes: 75 additions & 14 deletions src/main/java/mekanism/common/content/qio/QIOGlobalItemLookup.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import mekanism.api.SerializationConstants;
Expand Down Expand Up @@ -36,6 +38,18 @@ private QIOGlobalItemLookup() {
// we only don't store them as such for the generic so that we don't have to create extra objects for purposes
// of getting the uuid for a given item type
private BiMap<UUID, HashedItem> itemCache = HashBiMap.create();
/**
* Map of "No longer valid" -> "New Id"
*/
private Map<UUID, UUID> mergedIds = Collections.emptyMap();

public boolean hasAliases() {
return !mergedIds.isEmpty();
}

public UUID getWinningId(UUID uuid) {
return mergedIds.getOrDefault(uuid, uuid);
}

@Nullable
public UUID getUUIDForType(HashedItem item) {
Expand Down Expand Up @@ -94,7 +108,19 @@ private static class QIOGlobalItemLookupDataHandler extends MekanismSavedData {

@Override
public void load(@NotNull CompoundTag nbt, @NotNull HolderLookup.Provider provider) {
//TODO - 1.19: Do we want to clear existing elements
boolean hasAliases = nbt.contains(SerializationConstants.ALIASES, Tag.TAG_COMPOUND);
if (hasAliases) {
loadAliases(nbt.getCompound(SerializationConstants.ALIASES));
}
if (nbt.contains(SerializationConstants.ITEMS, Tag.TAG_COMPOUND)) {
loadItemData(nbt.getCompound(SerializationConstants.ITEMS), provider);
} else if (!hasAliases) {
//TODO - 1.22: Remove this legacy way of falling back to assuming the entire nbt is the item data
loadItemData(nbt, provider);
}
}

private void loadItemData(@NotNull CompoundTag nbt, @NotNull HolderLookup.Provider provider) {
for (String key : nbt.getAllKeys()) {
UUID uuid;
try {
Expand All @@ -112,17 +138,61 @@ public void load(@NotNull CompoundTag nbt, @NotNull HolderLookup.Provider provid
//Note: We can't cache the nbt we read from as something might have changed related to caps just from loading it, and we
// want to make sure that we save it with the proper corresponding data
//TODO: Eventually we may want to keep the NBT so that if the mod gets added back it exists again
QIOGlobalItemLookup.INSTANCE.itemCache.put(uuid, new SerializedHashedItem(stack));
SerializedHashedItem item = new SerializedHashedItem(stack);
try {
QIOGlobalItemLookup.INSTANCE.itemCache.put(uuid, item);
} catch (IllegalArgumentException e) {
UUID winningId = QIOGlobalItemLookup.INSTANCE.itemCache.inverse().get(item);
if (winningId == null) {
Mekanism.logger.error("Failed to resolve conflict for UUID ({}) for item {} with components: {}. Skipping", uuid, stack.getItem(),
stack.getComponentsPatch());
} else {
Mekanism.logger.warn("Adding alias between UUID ({}) to ({}) for item {} with components: {}", uuid, winningId, stack.getItem(),
stack.getComponentsPatch());
//Try to add it as an alias
if (QIOGlobalItemLookup.INSTANCE.mergedIds.isEmpty()) {
QIOGlobalItemLookup.INSTANCE.mergedIds = new HashMap<>();
}
QIOGlobalItemLookup.INSTANCE.mergedIds.put(uuid, winningId);
}
}
}
}
}

private void loadAliases(CompoundTag tag) {
if (!tag.isEmpty() && QIOGlobalItemLookup.INSTANCE.mergedIds.isEmpty()) {
QIOGlobalItemLookup.INSTANCE.mergedIds = new HashMap<>();
}
for (String key : tag.getAllKeys()) {
try {
//Note: Either of these might throw an IllegalArgumentException
UUID uuid = UUID.fromString(key);
UUID winningId = tag.getUUID(key);
QIOGlobalItemLookup.INSTANCE.mergedIds.put(uuid, winningId);
} catch (IllegalArgumentException e) {
Mekanism.logger.warn("Invalid alias UUID ({}) or winningId UUID stored in {} saved data.", key, DATA_HANDLER_NAME);
}
}
}

@NotNull
@Override
public CompoundTag save(@NotNull CompoundTag nbt, @NotNull HolderLookup.Provider provider) {
//TODO - 1.19: See if we can further improve this
for (Map.Entry<UUID, HashedItem> entry : QIOGlobalItemLookup.INSTANCE.itemCache.entrySet()) {
nbt.put(entry.getKey().toString(), ((SerializedHashedItem) entry.getValue()).getNbtRepresentation(provider));
if (!QIOGlobalItemLookup.INSTANCE.mergedIds.isEmpty()) {
//Ensure we persist and aliases, as we don't want someone losing data if their chunks weren't loaded
CompoundTag aliases = new CompoundTag();
for (Map.Entry<UUID, UUID> entry : QIOGlobalItemLookup.INSTANCE.mergedIds.entrySet()) {
aliases.putUUID(entry.getKey().toString(), entry.getValue());
}
nbt.put(SerializationConstants.ALIASES, aliases);
}
if (!QIOGlobalItemLookup.INSTANCE.itemCache.isEmpty()) {
CompoundTag items = new CompoundTag();
for (Map.Entry<UUID, HashedItem> entry : QIOGlobalItemLookup.INSTANCE.itemCache.entrySet()) {
items.put(entry.getKey().toString(), ((SerializedHashedItem) entry.getValue()).getNbtRepresentation(provider));
}
nbt.put(SerializationConstants.ITEMS, items);
}
return nbt;
}
Expand All @@ -132,15 +202,6 @@ private static class SerializedHashedItem extends HashedItem {

private Tag nbtRepresentation;

@Nullable
protected static SerializedHashedItem read(HolderLookup.Provider provider, CompoundTag nbtRepresentation) {
ItemStack stack = ItemStack.parseOptional(provider, nbtRepresentation);
//If the stack is empty something went wrong so return null, otherwise just create a new serializable hashed item
// We can't cache the nbt we read from as something might have changed related to caps just from loading it, and we
// want to make sure that we save it with the proper corresponding data
return stack.isEmpty() ? null : new SerializedHashedItem(stack);
}

private SerializedHashedItem(ItemStack stack) {
super(stack);
}
Expand Down

0 comments on commit 84e41fb

Please sign in to comment.