9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2025-12-19 15:09:15 +00:00

缓存清理

This commit is contained in:
XiaoMoMi
2025-09-30 02:44:14 +08:00
parent ba1fef4de7
commit eda7ff749d
5 changed files with 198 additions and 26 deletions

View File

@@ -1,14 +1,22 @@
package net.momirealms.craftengine.bukkit.plugin.command.feature;
import net.momirealms.craftengine.bukkit.api.CraftEngineItems;
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
import net.momirealms.craftengine.bukkit.font.BukkitFontManager;
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature;
import net.momirealms.craftengine.core.block.BlockStateWrapper;
import net.momirealms.craftengine.core.block.CustomBlock;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.font.BitmapImage;
import net.momirealms.craftengine.core.item.CustomItem;
import net.momirealms.craftengine.core.pack.allocator.IdAllocator;
import net.momirealms.craftengine.core.pack.allocator.VisualBlockStateAllocator;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager;
import net.momirealms.craftengine.core.util.FileUtils;
import net.momirealms.craftengine.core.util.Key;
import org.bukkit.command.CommandSender;
import org.bukkit.inventory.ItemStack;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.incendo.cloud.Command;
import org.incendo.cloud.context.CommandContext;
@@ -18,11 +26,11 @@ import org.incendo.cloud.suggestion.Suggestion;
import org.incendo.cloud.suggestion.SuggestionProvider;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class DebugCleanCacheCommand extends BukkitCommandFeature<CommandSender> {
@@ -48,9 +56,14 @@ public class DebugCleanCacheCommand extends BukkitCommandFeature<CommandSender>
switch (type) {
case "custom-model-data" -> {
BukkitItemManager instance = BukkitItemManager.instance();
Set<String> ids = CraftEngineItems.loadedItems().keySet().stream().map(Key::toString).collect(Collectors.toSet());
Map<Key, Set<String>> idsMap = new HashMap<>();
for (CustomItem<ItemStack> item : instance.loadedItems().values()) {
Set<String> ids = idsMap.computeIfAbsent(item.clientBoundMaterial(), k -> new HashSet<>());
ids.add(item.id().asString());
}
int total = 0;
for (Map.Entry<Key, IdAllocator> entry : instance.itemParser().idAllocators().entrySet()) {
for (Map.Entry<Key, IdAllocator> entry : getAllCachedCustomModelData().entrySet()) {
Set<String> ids = idsMap.getOrDefault(entry.getKey(), Collections.emptySet());
List<String> removed = entry.getValue().cleanupUnusedIds(i -> !ids.contains(i));
total += removed.size();
try {
@@ -60,18 +73,82 @@ public class DebugCleanCacheCommand extends BukkitCommandFeature<CommandSender>
return;
}
for (String id : removed) {
this.plugin().logger().info("Cleaned unused item: " + id);
this.plugin().logger().info("Cleaned unsued item: " + id);
}
}
context.sender().sendMessage("Cleaned " + total + " unused custom model data");
}
case "custom-block-states" -> {
}
case "visual-block-states" -> {
}
case "font", "images" -> {
BukkitFontManager instance = this.plugin().fontManager();
Map<Key, Set<String>> idsMap = new HashMap<>();
for (BitmapImage image : instance.loadedImages().values()) {
Set<String> ids = idsMap.computeIfAbsent(image.font(), k -> new HashSet<>());
String id = image.id().toString();
ids.add(id);
for (int i = 0; i < image.rows(); i++) {
for (int j = 0; j < image.columns(); j++) {
String imageArgs = id + ":" + i + ":" + j;
ids.add(imageArgs);
}
}
}
int total = 0;
for (Map.Entry<Key, IdAllocator> entry : getAllCachedFont().entrySet()) {
Key font = entry.getKey();
Set<String> ids = idsMap.getOrDefault(font, Collections.emptySet());
List<String> removed = entry.getValue().cleanupUnusedIds(i -> !ids.contains(i));
try {
entry.getValue().saveToCache();
} catch (IOException e) {
this.plugin().logger().warn("Error while saving codepoint allocation for font " + font.asString(), e);
return;
}
for (String id : removed) {
this.plugin().logger().info("Cleaned unsued image: " + id);
}
total += removed.size();
}
context.sender().sendMessage("Cleaned " + total + " unused codepoints");
}
case "custom-block-states" -> {
BukkitBlockManager instance = BukkitBlockManager.instance();
Set<String> ids = new HashSet<>();
for (CustomBlock customBlock : instance.loadedBlocks().values()) {
for (ImmutableBlockState state : customBlock.variantProvider().states()) {
ids.add(state.toString());
}
}
IdAllocator idAllocator = instance.blockParser().internalIdAllocator();
List<String> removed = idAllocator.cleanupUnusedIds(i -> !ids.contains(i));
try {
idAllocator.saveToCache();
} catch (IOException e) {
this.plugin().logger().warn("Error while saving custom block states allocation", e);
}
for (String id : removed) {
this.plugin().logger().info("Cleaned unsued block state: " + id);
}
context.sender().sendMessage("Cleaned " + removed.size() + " unused custom block states");
}
case "visual-block-states" -> {
BukkitBlockManager instance = BukkitBlockManager.instance();
Set<BlockStateWrapper> ids = new HashSet<>();
for (CustomBlock customBlock : instance.loadedBlocks().values()) {
for (ImmutableBlockState state : customBlock.variantProvider().states()) {
ids.add(state.vanillaBlockState());
}
}
VisualBlockStateAllocator visualBlockStateAllocator = instance.blockParser().visualBlockStateAllocator();
List<String> removed = visualBlockStateAllocator.cleanupUnusedIds(i -> !ids.contains(i));
try {
visualBlockStateAllocator.saveToCache();
} catch (IOException e) {
this.plugin().logger().warn("Error while saving visual block states allocation", e);
}
for (String id : removed) {
this.plugin().logger().info("Cleaned unsued block appearance: " + id);
}
context.sender().sendMessage("Cleaned " + removed.size() + " unused block state appearances");
}
}
});
@@ -81,4 +158,71 @@ public class DebugCleanCacheCommand extends BukkitCommandFeature<CommandSender>
public String getFeatureID() {
return "debug_clean_cache";
}
public Map<Key, IdAllocator> getAllCachedCustomModelData() {
Path cacheDir = CraftEngine.instance().dataFolderPath().resolve("cache").resolve("custom-model-data");
Map<Key, IdAllocator> idAllocators = new HashMap<>();
try (Stream<Path> files = Files.list(cacheDir)) {
files.filter(this::isJsonFile)
.forEach(file -> processIdAllocatorFile(cacheDir, file, idAllocators));
} catch (IOException e) {
CraftEngine.instance().logger().warn("Failed to process: " + cacheDir.getFileName(), e);
}
return idAllocators;
}
public Map<Key, IdAllocator> getAllCachedFont() {
Path cacheDir = CraftEngine.instance().dataFolderPath().resolve("cache").resolve("font");
try {
List<Path> namespaces = FileUtils.collectNamespaces(cacheDir);
Map<Key, IdAllocator> idAllocators = new HashMap<>();
for (Path namespace : namespaces) {
processNamespace(namespace, idAllocators);
}
return idAllocators;
} catch (IOException e) {
CraftEngine.instance().logger().warn("Failed to load cached id allocators from: " + cacheDir, e);
return Collections.emptyMap();
}
}
private void processNamespace(Path namespace, Map<Key, IdAllocator> idAllocators) {
if (!Files.isDirectory(namespace)) {
return;
}
try (Stream<Path> files = Files.list(namespace)) {
files.filter(this::isJsonFile)
.forEach(file -> processIdAllocatorFile(namespace, file, idAllocators));
} catch (IOException e) {
CraftEngine.instance().logger().warn("Failed to process namespace: " + namespace.getFileName(), e);
}
}
private boolean isJsonFile(Path file) {
return Files.isRegularFile(file) && file.getFileName().toString().endsWith(".json");
}
private void processIdAllocatorFile(Path namespace, Path file, Map<Key, IdAllocator> idAllocators) {
try {
String namespaceName = namespace.getFileName().toString();
String fileName = FileUtils.pathWithoutExtension(file.getFileName().toString());
Key font = Key.of(namespaceName, fileName);
IdAllocator allocator = new IdAllocator(file);
allocator.loadFromCache();
idAllocators.put(font, allocator);
} catch (Exception e) {
CraftEngine.instance().logger().warn("Failed to load id allocator from: " + file, e);
}
}
}

View File

@@ -278,7 +278,7 @@ warning.config.block.state.entity_renderer.model_engine.missing_model: "<yellow>
warning.config.block.state.variant.invalid_appearance: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' has an error that the variant '<arg:2>' is using a non-existing appearance '<arg:3>'.</yellow>"
warning.config.block.state.invalid_vanilla: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' is using an invalid vanilla block state '<arg:2>'.</yellow>"
warning.config.block.state.invalid_auto_state: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' is using an invalid auto-state '<arg:2>'. Allowed values: [<arg:3>].</yellow>"
warning.config.block.state.auto_state.exhausted: "<yellow>Issue found in file <arg:0> - Cannot allocate visual block state for block '<arg:1>' as the slots('<arg:3>') in group '<arg:2>' have been exhausted.</yellow>"
warning.config.block.state.auto_state.exhausted: "<yellow>Issue found in file <arg:0> - The visual state group '<arg:2>' has reached its maximum capacity of '<arg:3>' slots and cannot allocate a state for block '<arg:1>'.</yellow>"
warning.config.block.state.unavailable_vanilla: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' is using an unavailable vanilla block state '<arg:2>'. Please free that state in block-state-mappings.</yellow>"
warning.config.block.state.invalid_vanilla_id: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' is using a vanilla block state '<arg:2>' that exceeds the available slot range '0~<arg:3>'.</yellow>"
warning.config.block.state.invalid_id: "<yellow>Issue found in file <arg:0> - The block state ID range (<arg:2>) used by block '<arg:1>' is outside the valid range of 0 to <arg:3>. Please add more server-side blocks in 'config.yml' if the current slots are exhausted.</yellow>"

View File

@@ -313,6 +313,14 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
this.pendingConfigSections.add(section);
}
public IdAllocator internalIdAllocator() {
return internalIdAllocator;
}
public VisualBlockStateAllocator visualBlockStateAllocator() {
return visualBlockStateAllocator;
}
@Override
public void postProcess() {
this.internalIdAllocator.processPendingAllocations();
@@ -514,7 +522,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
}
}
CompletableFutures.allOf(futureVisualStates.values()).whenComplete((v2, t2) -> {
CompletableFutures.allOf(futureVisualStates.values()).whenComplete((v2, t2) -> ResourceConfigUtils.runCatching(path, node, () -> {
if (t2 != null) {
if (t2 instanceof CompletionException e) {
Throwable cause = e.getCause();
@@ -635,7 +643,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
// 抛出次要警告
exceptionCollector.throwIfPresent();
});
}, () -> GsonHelper.get().toJson(section)));
}, () -> GsonHelper.get().toJson(section)));
}

View File

@@ -32,6 +32,7 @@ import java.util.concurrent.ExecutionException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public abstract class AbstractFontManager implements FontManager {
private final CraftEngine plugin;
@@ -511,10 +512,6 @@ public abstract class AbstractFontManager implements FontManager {
});
}
public Map<Key, IdAllocator> idAllocators() {
return this.idAllocators;
}
@Override
public void parseSection(Pack pack, Path path, String node, Key id, Map<String, Object> section) {
if (AbstractFontManager.this.images.containsKey(id)) {

View File

@@ -1,5 +1,7 @@
package net.momirealms.craftengine.core.pack.allocator;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
@@ -15,6 +17,7 @@ import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import java.util.function.Predicate;
public class VisualBlockStateAllocator {
private final Path cacheFilePath;
@@ -32,7 +35,9 @@ public class VisualBlockStateAllocator {
}
public void reset() {
Arrays.fill(this.pendingAllocationFutures, new ArrayList<>());
for (int i = 0; i < this.pendingAllocationFutures.length; i++) {
this.pendingAllocationFutures[i] = new ArrayList<>();
}
this.cachedBlockStates.clear();
this.pendingAllocations.clear();
}
@@ -53,6 +58,19 @@ public class VisualBlockStateAllocator {
return future;
}
public List<String> cleanupUnusedIds(Predicate<BlockStateWrapper> shouldRemove) {
List<String> idsToRemove = new ArrayList<>();
for (Map.Entry<String, BlockStateWrapper> entry : this.cachedBlockStates.entrySet()) {
if (shouldRemove.test(entry.getValue())) {
idsToRemove.add(entry.getKey());
}
}
for (String id : idsToRemove) {
this.cachedBlockStates.remove(id);
}
return idsToRemove;
}
public void processPendingAllocations() {
// 先处理缓存的
for (Map.Entry<String, BlockStateWrapper> entry : this.cachedBlockStates.entrySet()) {
@@ -64,12 +82,17 @@ public class VisualBlockStateAllocator {
if (!candidate.isUsed()) {
// 获取当前的安排任务
Pair<AutoStateGroup, CompletableFuture<BlockStateWrapper>> pair = this.pendingAllocations.get(entry.getKey());
// 如果候选满足组,那么直接允许起飞
if (pair != null && pair.left().test(candidate.blockState())) {
pair.right().complete(candidate.blockState());
if (pair != null) {
// 如果候选满足组,那么直接允许起飞
if (pair.left().test(candidate.blockState())) {
pair.right().complete(candidate.blockState());
} else {
// 不满足候选组要求,那就等着分配新的吧
}
} else {
// 尽管未被使用,该槽位也应该被占用,以避免被自动分配到
candidate.setUsed();
}
// 尽管未被使用,该槽位也应该被占用,以避免被自动分配到
candidate.setUsed();
}
// 被使用了就随他去
}