mirror of
https://github.com/Xiao-MoMi/craft-engine.git
synced 2025-12-19 15:09:15 +00:00
缓存清理
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>"
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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,13 +82,18 @@ public class VisualBlockStateAllocator {
|
||||
if (!candidate.isUsed()) {
|
||||
// 获取当前的安排任务
|
||||
Pair<AutoStateGroup, CompletableFuture<BlockStateWrapper>> pair = this.pendingAllocations.get(entry.getKey());
|
||||
if (pair != null) {
|
||||
// 如果候选满足组,那么直接允许起飞
|
||||
if (pair != null && pair.left().test(candidate.blockState())) {
|
||||
if (pair.left().test(candidate.blockState())) {
|
||||
pair.right().complete(candidate.blockState());
|
||||
} else {
|
||||
// 不满足候选组要求,那就等着分配新的吧
|
||||
}
|
||||
} else {
|
||||
// 尽管未被使用,该槽位也应该被占用,以避免被自动分配到
|
||||
candidate.setUsed();
|
||||
}
|
||||
}
|
||||
// 被使用了就随他去
|
||||
}
|
||||
// 没有候选也随他去
|
||||
|
||||
Reference in New Issue
Block a user