Skip to content

Commit

Permalink
add: IDManager same type elements handling and some minor fixes;
Browse files Browse the repository at this point in the history
  • Loading branch information
MJaroslav committed May 26, 2024
1 parent 04d7420 commit 1328bc8
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

import java.util.Arrays;

Expand All @@ -31,14 +32,18 @@ public abstract class MixinEnchantment {
@ModifyVariable(method = "<init>", at = @At(value = "FIELD", target = "Lnet/minecraft/enchantment/Enchantment;effectId:I",
shift = Shift.BEFORE, ordinal = 0), ordinal = 0)
private int mjtuils$init(int original) {
return IDManagerModule.ENCHANTMENTS.registerID(getClass(), original);
return IDManagerModule.ENCHANTMENTS.registerId(getClass(), original);
}

@Inject(method = "setName", at = @At("HEAD"))
private void inject$addNameForReiteration(@NotNull String name, @NotNull CallbackInfoReturnable<Enchantment> ci) {
IDManagerModule.ENCHANTMENTS.setComment(effectId, name);
}

// Extends enchantments array.
@Inject(method = "<clinit>", at = @At("TAIL"))
private static void mjtuils$cinit(@NotNull CallbackInfo ci) {
enchantmentsList = Arrays.copyOf(enchantmentsList, Enchantments.newArraySize);
MJUtilsInfo.LOG_LIB.debug("Enchantments array size changed to " + Enchantments.newArraySize);
MJUtilsInfo.LOG_LIB.debug("Enchantments array size changed to {}", Enchantments.newArraySize);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

import java.util.Arrays;

Expand All @@ -31,13 +32,18 @@ public abstract class MixinPotion {
@ModifyVariable(method = "<init>", at = @At(value = "FIELD", target = "Lnet/minecraft/potion/Potion;id:I",
shift = Shift.BEFORE, ordinal = 0), ordinal = 0)
private int init(int original) {
return IDManagerModule.POTIONS.registerID(getClass(), original);
return IDManagerModule.POTIONS.registerId(getClass(), original);
}

@Inject(method = "setPotionName", at = @At("HEAD"))
private void inject$addNameForReiteration(@NotNull String name, @NotNull CallbackInfoReturnable<Potion> ci) {
IDManagerModule.POTIONS.setComment(id, name);
}

// Extends potions array.
@Inject(method = "<clinit>", at = @At("TAIL"))
private static void cinit(@NotNull CallbackInfo ci) {
potionTypes = Arrays.copyOf(potionTypes, Potions.newArraySize);
MJUtilsInfo.LOG_LIB.debug("Potions array size changed to " + Potions.newArraySize);
MJUtilsInfo.LOG_LIB.debug("Potions array size changed to {}", Potions.newArraySize);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package io.github.mjaroslav.mjutils.internal.common.modular;

import cpw.mods.fml.common.event.FMLConstructionEvent;
import cpw.mods.fml.common.event.FMLLoadCompleteEvent;
import io.github.mjaroslav.mjutils.asm.MixinPatches.Enchantments;
import io.github.mjaroslav.mjutils.asm.MixinPatches.Potions;
Expand All @@ -23,13 +22,8 @@ Enchantments.newArraySize, concat(concat(concat(concat(range(0, 8), range(16, 22
range(48, 52)), range(61, 63)).boxed().collect(Collectors.toSet()), Enchantments.occupiedPolicy,
Paths.get("config", "enchantments.properties"));

public void listen(@NotNull FMLConstructionEvent event) {
POTIONS.loadIDsFromFileIfEnabled();
ENCHANTMENTS.loadIDsFromFileIfEnabled();
}

public void listen(@NotNull FMLLoadCompleteEvent event) {
POTIONS.saveIDsToFileIfEnabled();
ENCHANTMENTS.saveIDsToFileIfEnabled();
POTIONS.complete();
ENCHANTMENTS.complete();
}
}
160 changes: 121 additions & 39 deletions src/main/java/io/github/mjaroslav/mjutils/internal/data/IDManager.java
Original file line number Diff line number Diff line change
@@ -1,90 +1,172 @@
package io.github.mjaroslav.mjutils.internal.data;

import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import io.github.mjaroslav.mjutils.config.PropertiesConfig;
import io.github.mjaroslav.mjutils.lib.MJUtilsInfo;
import io.github.mjaroslav.mjutils.util.UtilsDesktop;
import lombok.val;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.nio.file.Path;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;

public class IDManager {
protected final Map<Integer, String> cache = new HashMap<>();
public static final String SEPARATOR = "@";

protected final BiMap<Integer, String> ids = HashBiMap.create();
protected final Map<String, Integer> repetitions = new HashMap<>();
protected final Map<Integer, String> comments = new HashMap<>();
protected final Map<Integer, Set<String>> conflictedIds = new HashMap<>();
protected final Collection<Integer> ignoredIndexes = new HashSet<>();
protected final String configComment;

protected final @NotNull String classSimpleName;
protected final boolean enabled;
protected final int arraySize;
protected final Collection<Integer> vanillaIndexes;
protected final @NotNull OccupiedPolicy policy;
protected final @NotNull PropertiesConfig config;

public IDManager(@NotNull String classSimpleName, boolean enabled, int arraySize,
@NotNull Collection<Integer> vanillaIndexes, @NotNull OccupiedPolicy policy, @NotNull Path configPath) {
@NotNull Collection<Integer> ignoredIndexes, @NotNull OccupiedPolicy policy,
@NotNull Path configPath) {
this.classSimpleName = classSimpleName;
this.enabled = enabled;
this.arraySize = arraySize;
this.vanillaIndexes = vanillaIndexes;
this.ignoredIndexes.addAll(ignoredIndexes);
this.policy = policy;
config = new PropertiesConfig(MJUtilsInfo.MOD_ID, configPath);
config.setComment(String.format("""
configComment = String.format("""
%1$s provides ID array size extension and adds ID occupied strategy.
This file used for "AUTO" strategy, then means that all %2$s registrations
will take new free ID if desired was occupied.
You can use this for overrides all added %2$s IDs,
but notice that it will not work for vanilla IDs.
Pattern: className=ID
%2$s array extended to %3$s, you can change it in configuration
Pattern: className%3$sN=ID,
where N is the number of the current repetition
in additional, it may have comment with name of element.
N value of each element may change when mod list was edited.
%2$s array extended to %4$s, you can change it in configuration
This comment will automatically regenerate on each game load with "AUTO" strategy.""",
MJUtilsInfo.NAME, classSimpleName, arraySize
));
MJUtilsInfo.NAME, classSimpleName, SEPARATOR, arraySize
);
config.setComment(configComment);
initialize();
}

public int registerID(@NotNull Class<?> type, int desiredId) {
if (policy == OccupiedPolicy.IGNORE || vanillaIndexes.contains(desiredId)) return desiredId; // Vanilla IDs
public @NotNull String getName(@NotNull Class<?> type) {
val name = type.getName();
if (policy == OccupiedPolicy.AUTO) {
val id = verifyID(name, config.getInt(name, desiredId), true, false);
config.setValue(name, id);
return id;
} else return verifyID(name, config.getInt(name, desiredId), true, false);
val repetition = repetitions.getOrDefault(name, 0) + 1;
repetitions.put(name, repetition);
return name + SEPARATOR + repetition;
}

public int verifyID(@NotNull String type, int desiredId, boolean throwException, boolean dryRun) {
if (cache.containsKey(desiredId)) {
if (policy == OccupiedPolicy.AUTO) {
val id = getNextAvailableID();
if (!dryRun) cache.put(desiredId, type);
return id;
public int registerId(@NotNull Class<?> type, int desiredId) {
if (!enabled || ignoredIndexes.contains(desiredId)) return desiredId;
val name = getName(type);
if (policy == OccupiedPolicy.AUTO)
desiredId = config.getInt(name, desiredId);
if (ids.containsKey(desiredId)) {
if (policy == OccupiedPolicy.CRASH_DUMP) {
conflictedIds.putIfAbsent(desiredId, new HashSet<>());
conflictedIds.get(desiredId).add(name);
desiredId = getNextAvailableIdFromEnd();
}
if (throwException)
throw new IllegalArgumentException(String.format("Occupied %1$s ID %2$s --> %3$s and %4$s",
classSimpleName, desiredId, type, cache.get(desiredId)));
if (policy == OccupiedPolicy.CRASH_FIRST)
throw new IllegalArgumentException(String.format("Occupied %1$s id %2$s --> %3$s and " +
"%4$s",
classSimpleName, desiredId, name, ids.get(desiredId)));
else desiredId = getNextAvailableId();
}
if (!dryRun) cache.put(desiredId, type);
ids.put(desiredId, name);
config.setValue(name, desiredId);
return desiredId;
}

public int getNextAvailableID() {
for (var id = 0; id < arraySize; id++) if (!vanillaIndexes.contains(id) && !cache.containsKey(id)) return id;
public void setComment(int id, @Nullable String comment) {
if (StringUtils.isEmpty(comment)) comments.remove(id);
else comments.put(id, comment);
}

public int getNextAvailableId() {
for (var id = 0; id < arraySize; id++) if (!ignoredIndexes.contains(id) && !ids.containsKey(id)) return id;
throw new ArrayIndexOutOfBoundsException(String.format("Can't generate ID for %1$s: out of free ids", classSimpleName));
}

public void loadIDsFromFileIfEnabled() {
public int getNextAvailableIdFromEnd() {
for (var id = arraySize - 1; id > 0; id--) if (!ignoredIndexes.contains(id) && !ids.containsKey(id)) return id;
throw new ArrayIndexOutOfBoundsException(String.format("Can't generate ID for %1$s: out of free ids", classSimpleName));
}

protected void initialize() {
if (!enabled || policy != OccupiedPolicy.AUTO) return;
config.load();
}

public void saveIDsToFileIfEnabled() {
if (!enabled || policy != OccupiedPolicy.AUTO) return;
config.save();
@Contract(" -> new")
public @NotNull List<@NotNull Integer> getAvailableIds() {
val result = new ArrayList<Integer>();
for (var id = 0; id < arraySize; id++) if (!ignoredIndexes.contains(id) && !ids.containsKey(id)) result.add(id);
return result;
}

public @NotNull String getFormattedElement(int id) {
var result = ids.get(id);
if (comments.containsKey(id)) result += " (" + comments.get(id) + ")";
return result;
}

public @NotNull String getFormatElement(@NotNull String name) {
return getFormattedElement(ids.inverse().get(name));
}

public void complete() {
if (!enabled) return;
if (policy == OccupiedPolicy.AUTO) {
comments.forEach((id, comment) -> config.setComment(ids.get(id), comment));
config.setComment(configComment);
config.save();
System.out.println(classSimpleName + " id manager saved values to file");
} else if (policy == OccupiedPolicy.CRASH_DUMP) {
MJUtilsInfo.LOG_LIB.error("Found conflicted {} ids!", classSimpleName);
conflictedIds.entrySet().stream().sorted(Comparator.comparingInt(Entry::getKey)).forEach(e ->
MJUtilsInfo.LOG_LIB.error("Id {} for next {} elements: {}", e.getKey(), classSimpleName,
e.getValue().stream().map(this::getFormatElement).collect(Collectors.joining(","))));

MJUtilsInfo.LOG_LIB.error("Available ids: {}", String.join(",", getRangedAvailableIds()));

UtilsDesktop.crashGame(new IllegalArgumentException(String.format("%s id conflict", classSimpleName)),
"See above to more info");
}
}

public @NotNull List<@NotNull String> getRangedAvailableIds() {
val ranges = new ArrayList<String>();
val available = getAvailableIds();
if (available.size() > 1) {
var begin = available.get(0);
var prev = begin;
for (int i = 0; i < available.size() - 1; i++) {
if (begin - i > 1) {
if (begin - prev == 0) ranges.add(String.valueOf(begin));
else ranges.add("[" + begin + "-" + prev + "]");
begin = i;
}
prev = i;
}
}
return ranges;
}

public enum OccupiedPolicy {
IGNORE, CRASH, AUTO
CRASH_FIRST, CRASH_DUMP, AUTO
}
}

0 comments on commit 1328bc8

Please sign in to comment.