mirror of
https://github.com/Xiao-MoMi/craft-engine.git
synced 2025-12-22 08:29:21 +00:00
feat(core): 添加外部数据构建循环检测
This commit is contained in:
@@ -13,7 +13,7 @@ public class SXItemSource implements ExternalItemSource<ItemStack> {
|
||||
|
||||
@Override
|
||||
public String plugin() {
|
||||
return "sxitem";
|
||||
return "sx-item";
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -7,10 +7,13 @@ import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextPar
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Deque;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||
|
||||
public class ItemBuildContext extends PlayerOptionalContext {
|
||||
public static final ItemBuildContext EMPTY = new ItemBuildContext(null, ContextHolder.EMPTY);
|
||||
private Deque<String> externalBuildStack;
|
||||
|
||||
public ItemBuildContext(@Nullable Player player, @NotNull ContextHolder contexts) {
|
||||
super(player, contexts);
|
||||
@@ -32,4 +35,16 @@ public class ItemBuildContext extends PlayerOptionalContext {
|
||||
if (player == null) return new ItemBuildContext(null, ContextHolder.EMPTY);
|
||||
return new ItemBuildContext(player, new ContextHolder(Map.of(DirectContextParameters.PLAYER, () -> player)));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Deque<String> getExternalBuildStack() {
|
||||
if (externalBuildStack == null) {
|
||||
externalBuildStack = new ConcurrentLinkedDeque<>();
|
||||
}
|
||||
return externalBuildStack;
|
||||
}
|
||||
|
||||
public void clearExternalBuildStack() {
|
||||
externalBuildStack = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,11 +9,11 @@ import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigExce
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
public class ExternalModifier<I> implements ItemDataModifier<I> {
|
||||
public static final Factory<?> FACTORY = new Factory<>();
|
||||
private static final ThreadLocal<Deque<String>> BUILD_STACK = ThreadLocal.withInitial(ArrayDeque::new);
|
||||
private final String id;
|
||||
private final ExternalItemSource<I> provider;
|
||||
|
||||
@@ -38,14 +38,36 @@ public class ExternalModifier<I> implements ItemDataModifier<I> {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Item<I> apply(Item<I> item, ItemBuildContext context) {
|
||||
I another = this.provider.build(id, context);
|
||||
if (another == null) {
|
||||
CraftEngine.instance().logger().warn("'" + id + "' could not be found in " + provider.plugin());
|
||||
String stackElement = provider.plugin() + "[id=" + id + "]";
|
||||
Deque<String> buildStack = BUILD_STACK.get();
|
||||
|
||||
if (buildStack.contains(stackElement)) {
|
||||
StringJoiner dependencyChain = new StringJoiner(" -> ");
|
||||
buildStack.forEach(dependencyChain::add);
|
||||
dependencyChain.add(stackElement);
|
||||
CraftEngine.instance().logger().warn("Item '" + item.customId().orElseGet(item::id) +
|
||||
"' encountered circular dependency while building external item '" + this.id +
|
||||
"' (from plugin '" + provider.plugin() + "'). Dependency chain: " + dependencyChain
|
||||
);
|
||||
return item;
|
||||
}
|
||||
Item<I> anotherWrapped = (Item<I>) CraftEngine.instance().itemManager().wrap(another);
|
||||
item.merge(anotherWrapped);
|
||||
return item;
|
||||
|
||||
buildStack.push(stackElement);
|
||||
try {
|
||||
I another = this.provider.build(this.id, context);
|
||||
if (another == null) {
|
||||
CraftEngine.instance().logger().warn("'" + this.id + "' could not be found in " + provider.plugin());
|
||||
return item;
|
||||
}
|
||||
Item<I> anotherWrapped = (Item<I>) CraftEngine.instance().itemManager().wrap(another);
|
||||
item.merge(anotherWrapped);
|
||||
return item;
|
||||
} finally {
|
||||
buildStack.pop();
|
||||
if (buildStack.isEmpty()) {
|
||||
BUILD_STACK.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class Factory<I> implements ItemDataModifierFactory<I> {
|
||||
|
||||
@@ -24,7 +24,6 @@ import net.momirealms.craftengine.core.pack.model.generation.ModelGeneration;
|
||||
import net.momirealms.craftengine.core.pack.model.generation.ModelGenerator;
|
||||
import net.momirealms.craftengine.core.pack.model.rangedisptach.CustomModelDataRangeDispatchProperty;
|
||||
import net.momirealms.craftengine.core.pack.obfuscation.ObfA;
|
||||
import net.momirealms.craftengine.core.pack.obfuscation.ResourcePackGenerationException;
|
||||
import net.momirealms.craftengine.core.pack.revision.Revision;
|
||||
import net.momirealms.craftengine.core.pack.revision.Revisions;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
@@ -248,16 +247,21 @@ public abstract class AbstractPackManager implements PackManager {
|
||||
// magicConstructor.newInstance(resourcePackPath(), resourcePackPath());
|
||||
Method magicMethod = ReflectionUtils.getMethod(magicClazz, void.class);
|
||||
assert magicMethod != null;
|
||||
this.zipGenerator = (p1, p2) -> {
|
||||
final String magicStr1 = StringUtils.fromBytes(new byte[]{5, 50, 36, 56, 34, 37, 52, 50, 7, 54, 52, 60, 16, 50, 57, 50, 37, 54, 35, 62, 56, 57, 18, 47, 52, 50, 39, 35, 62, 56, 57}, 87);
|
||||
final String magicStr2 = StringUtils.fromBytes(new byte[]{4, 35, 43, 46, 39, 38, 98, 54, 45, 98, 37, 39, 44, 39, 48, 35, 54, 39, 98, 48, 39, 49, 45, 55, 48, 33, 39, 98, 50, 35, 33, 41, 120, 98}, 66);
|
||||
final String magicStr3 = StringUtils.fromBytes(new byte[]{107, 76, 68, 65, 72, 73, 13, 89, 66, 13, 74, 72, 67, 72, 95, 76, 89, 72, 13, 87, 68, 93, 13, 75, 68, 65, 72, 94, 39}, 45);
|
||||
ReflectionUtils.getDeclaredField(getClass().getSuperclass(), StringUtils.fromBytes(new byte[]{69, 86, 79, 120, 90, 81, 90, 77, 94, 75, 80, 77}, 63)).set(this, (BiConsumer<?, ?>) (p1, p2) -> {
|
||||
try {
|
||||
Object magicObject = magicConstructor.newInstance(p1, p2);
|
||||
magicMethod.invoke(magicObject);
|
||||
} catch (ResourcePackGenerationException e) {
|
||||
this.plugin.logger().warn("Failed to generate resource pack: " + e.getMessage());
|
||||
} catch (Exception e) {
|
||||
this.plugin.logger().warn("Failed to generate zip files\n" + new StringWriter(){{e.printStackTrace(new PrintWriter(this));}}.toString().replaceAll("\\.[Il]{2,}", "").replaceAll("/[Il]{2,}", ""));
|
||||
} catch (Throwable e) {
|
||||
if (e.getClass().getSimpleName().equals(magicStr1)) {
|
||||
this.plugin.logger().warn(magicStr2 + e.getMessage());
|
||||
} else {
|
||||
this.plugin.logger().warn(magicStr3 + new StringWriter(){{e.printStackTrace(new PrintWriter(this));}}.toString().replaceAll("\\.[Il]{2,}", "").replaceAll("/[Il]{2,}", ""));
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
} else {
|
||||
this.plugin.logger().warn("Magic class doesn't exist");
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package net.momirealms.craftengine.core.pack;
|
||||
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.plugin.config.Config;
|
||||
import net.momirealms.craftengine.core.plugin.logger.Debugger;
|
||||
import net.momirealms.craftengine.core.util.SetMonitor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.nio.file.Files;
|
||||
@@ -14,16 +16,26 @@ public class PackCacheData {
|
||||
private final Set<Path> externalFolders;
|
||||
|
||||
PackCacheData(@NotNull CraftEngine plugin) {
|
||||
this.externalFolders = Config.foldersToMerge().stream()
|
||||
.map(it -> plugin.dataFolderPath().getParent().resolve(it))
|
||||
.filter(Files::exists)
|
||||
.collect(Collectors.toSet());
|
||||
this.externalZips = Config.zipsToMerge().stream()
|
||||
.map(it -> plugin.dataFolderPath().getParent().resolve(it))
|
||||
.filter(Files::exists)
|
||||
.filter(Files::isRegularFile)
|
||||
.filter(file -> file.getFileName().toString().endsWith(".zip"))
|
||||
.collect(Collectors.toSet());
|
||||
this.externalFolders = new SetMonitor<>(
|
||||
Config.foldersToMerge().stream()
|
||||
.map(it -> plugin.dataFolderPath().getParent().resolve(it))
|
||||
.filter(Files::exists)
|
||||
.collect(Collectors.toSet()),
|
||||
add -> Debugger.RESOURCE_PACK.debug(() -> "Adding external folder: " + add),
|
||||
remove -> Debugger.RESOURCE_PACK.debug(() -> "Removing external folder: " + remove),
|
||||
true
|
||||
);
|
||||
this.externalZips = new SetMonitor<>(
|
||||
Config.zipsToMerge().stream()
|
||||
.map(it -> plugin.dataFolderPath().getParent().resolve(it))
|
||||
.filter(Files::exists)
|
||||
.filter(Files::isRegularFile)
|
||||
.filter(file -> file.getFileName().toString().endsWith(".zip"))
|
||||
.collect(Collectors.toSet()),
|
||||
add -> Debugger.RESOURCE_PACK.debug(() -> "Adding external zip: " + add),
|
||||
remove -> Debugger.RESOURCE_PACK.debug(() -> "Removing external zip: " + remove),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
@@ -15,12 +15,17 @@ public class ListMonitor<T> implements List<T> {
|
||||
private final Consumer<Object> removeConsumer;
|
||||
|
||||
public ListMonitor(List<T> list, Consumer<T> addConsumer, Consumer<Object> removeConsumer) {
|
||||
for (T key : list) {
|
||||
addConsumer.accept(key);
|
||||
}
|
||||
this(list, addConsumer, removeConsumer, false);
|
||||
}
|
||||
|
||||
public ListMonitor(List<T> list, Consumer<T> addConsumer, Consumer<Object> removeConsumer, boolean skipInitialNotification) {
|
||||
this.list = list;
|
||||
this.addConsumer = addConsumer;
|
||||
this.removeConsumer = removeConsumer;
|
||||
if (skipInitialNotification) return;
|
||||
for (T key : list) {
|
||||
this.addConsumer.accept(key);
|
||||
}
|
||||
}
|
||||
|
||||
public List<T> list() {
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
package net.momirealms.craftengine.core.util;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class SetMonitor<E> implements Set<E> {
|
||||
private final Set<E> set;
|
||||
private final Consumer<E> addConsumer;
|
||||
private final Consumer<Object> removeConsumer;
|
||||
|
||||
public SetMonitor(Set<E> set, Consumer<E> addConsumer, Consumer<Object> removeConsumer) {
|
||||
this(set, addConsumer, removeConsumer, false);
|
||||
}
|
||||
|
||||
public SetMonitor(Set<E> set, Consumer<E> addConsumer, Consumer<Object> removeConsumer, boolean skipInitialNotification) {
|
||||
this.set = set;
|
||||
this.addConsumer = addConsumer;
|
||||
this.removeConsumer = removeConsumer;
|
||||
if (skipInitialNotification) return;
|
||||
for (E element : set) {
|
||||
this.addConsumer.accept(element);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(E e) {
|
||||
this.addConsumer.accept(e);
|
||||
return this.set.add(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
this.removeConsumer.accept(o);
|
||||
return this.set.remove(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(@NotNull Collection<? extends E> c) {
|
||||
for (E element : c) {
|
||||
this.addConsumer.accept(element);
|
||||
}
|
||||
return this.set.addAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(@NotNull Collection<?> c) {
|
||||
for (Object o : c) {
|
||||
this.removeConsumer.accept(o);
|
||||
}
|
||||
return this.set.removeAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
for (E element : this.set) {
|
||||
this.removeConsumer.accept(element);
|
||||
}
|
||||
this.set.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return this.set.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return this.set.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
return this.set.contains(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Iterator<E> iterator() {
|
||||
return this.set.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Object @NotNull [] toArray() {
|
||||
return this.set.toArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull <T> T @NotNull [] toArray(@NotNull T[] a) {
|
||||
return this.set.toArray(a);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAll(@NotNull Collection<?> c) {
|
||||
return this.set.containsAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(@NotNull Collection<?> c) {
|
||||
return this.set.retainAll(c);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package net.momirealms.craftengine.core.util;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public final class StringUtils {
|
||||
private StringUtils() {}
|
||||
|
||||
@@ -39,4 +41,12 @@ public final class StringUtils {
|
||||
}
|
||||
return new String(chars);
|
||||
}
|
||||
|
||||
public static String fromBytes(byte[] bytes, int index) {
|
||||
byte[] decodedBytes = new byte[bytes.length];
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
decodedBytes[i] = (byte) (bytes[i] ^ ((byte) index));
|
||||
}
|
||||
return new String(decodedBytes, StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user