9
0
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:
jhqwqmc
2025-09-07 07:03:15 +08:00
parent ff4efa13a0
commit 439a936bf9
8 changed files with 201 additions and 29 deletions

View File

@@ -13,7 +13,7 @@ public class SXItemSource implements ExternalItemSource<ItemStack> {
@Override
public String plugin() {
return "sxitem";
return "sx-item";
}
@Nullable

View File

@@ -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;
}
}

View File

@@ -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> {

View File

@@ -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");
}

View File

@@ -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

View File

@@ -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() {

View File

@@ -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);
}
}

View File

@@ -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);
}
}