9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2025-12-31 04:46:37 +00:00

外观方块分配 未测试

This commit is contained in:
XiaoMoMi
2025-09-30 01:14:40 +08:00
parent 48bdf5f4e0
commit a85b94392e
39 changed files with 975 additions and 477 deletions

View File

@@ -18,9 +18,9 @@ import net.momirealms.craftengine.core.pack.LoadingSequence;
import net.momirealms.craftengine.core.pack.Pack;
import net.momirealms.craftengine.core.pack.PendingConfigSection;
import net.momirealms.craftengine.core.pack.ResourceLocation;
import net.momirealms.craftengine.core.pack.allocator.BlockStateAllocator;
import net.momirealms.craftengine.core.pack.allocator.BlockStateCandidate;
import net.momirealms.craftengine.core.pack.allocator.IdAllocator;
import net.momirealms.craftengine.core.pack.allocator.VisualBlockStateAllocator;
import net.momirealms.craftengine.core.pack.model.generation.AbstractModelGenerator;
import net.momirealms.craftengine.core.pack.model.generation.ModelGeneration;
import net.momirealms.craftengine.core.plugin.CraftEngine;
@@ -60,8 +60,6 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
protected final List<Suggestion> cachedSuggestions = new ArrayList<>();
// 缓存的使用中的命名空间
protected final Set<String> namespacesInUse = new HashSet<>();
// 用于检测单个外观方块状态是否被绑定了不同模型
protected final Map<Integer, JsonElement> tempVanillaBlockStateModels = new Int2ObjectOpenHashMap<>();
// Map<方块类型, Map<方块状态NBT,模型>>用于生成block state json
protected final Map<Key, Map<String, JsonElement>> blockStateOverrides = new HashMap<>();
// 用于生成mod使用的block state json
@@ -81,7 +79,9 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
// 自定义状态列表,会随着重载变化
protected final ImmutableBlockState[] immutableBlockStates;
// 倒推缓存
protected final BlockStateCandidate[] reversedBlockStateArranger;
protected final BlockStateCandidate[] autoVisualBlockStateCandidates;
// 用于检测单个外观方块状态是否被绑定了不同模型
protected final JsonElement[] tempVanillaBlockStateModels;
// 临时存储哪些视觉方块被使用了
protected final Set<BlockStateWrapper> tempVisualBlockStatesInUse = new HashSet<>();
protected final Set<Key> tempVisualBlocksInUse = new HashSet<>();
@@ -91,14 +91,15 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
protected AbstractBlockManager(CraftEngine plugin, int vanillaBlockStateCount, int customBlockCount) {
super(plugin);
this.vanillaBlockStateCount = vanillaBlockStateCount;
this.blockParser = new BlockParser();
this.blockStateMappingParser = new BlockStateMappingParser();
this.customBlocks = new DelegatingBlock[customBlockCount];
this.customBlockHolders = new Object[customBlockCount];
this.customBlockStates = new DelegatingBlockState[customBlockCount];
this.immutableBlockStates = new ImmutableBlockState[customBlockCount];
this.blockStateMappings = new int[customBlockCount + vanillaBlockStateCount];
this.reversedBlockStateArranger = new BlockStateCandidate[vanillaBlockStateCount];
this.autoVisualBlockStateCandidates = new BlockStateCandidate[vanillaBlockStateCount];
this.tempVanillaBlockStateModels = new JsonElement[vanillaBlockStateCount];
this.blockParser = new BlockParser(this.autoVisualBlockStateCandidates);
this.blockStateMappingParser = new BlockStateMappingParser();
Arrays.fill(this.blockStateMappings, -1);
}
@@ -130,7 +131,10 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
this.appearanceToRealState.clear();
Arrays.fill(this.blockStateMappings, -1);
Arrays.fill(this.immutableBlockStates, EmptyBlock.STATE);
Arrays.fill(this.reversedBlockStateArranger, null);
Arrays.fill(this.autoVisualBlockStateCandidates, null);
for (AutoStateGroup autoStateGroup : AutoStateGroup.values()) {
autoStateGroup.reset();
}
}
@Override
@@ -188,7 +192,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
}
protected void clearCache() {
this.tempVanillaBlockStateModels.clear();
Arrays.fill(this.tempVanillaBlockStateModels, null);
this.tempVisualBlockStatesInUse.clear();
this.tempVisualBlocksInUse.clear();
}
@@ -275,26 +279,10 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
Key blockOwnerId = getBlockOwnerId(beforeState);
List<BlockStateWrapper> blockStateWrappers = AbstractBlockManager.this.blockStateArranger.computeIfAbsent(blockOwnerId, k -> new ArrayList<>());
blockStateWrappers.add(beforeState);
AbstractBlockManager.this.reversedBlockStateArranger[beforeState.registryId()] = blockParser.createVisualBlockCandidate(beforeState);
AbstractBlockManager.this.autoVisualBlockStateCandidates[beforeState.registryId()] = createVisualBlockCandidate(beforeState);
}
exceptionCollector.throwIfPresent();
}
}
public class BlockParser extends IdSectionConfigParser {
public static final String[] CONFIG_SECTION_NAME = new String[]{"blocks", "block"};
private final IdAllocator internalIdAllocator;
private final List<PendingConfigSection> pendingConfigSections = new ArrayList<>();
private final BlockStateAllocator[] visualBlockStateAllocators = new BlockStateAllocator[AutoStateGroup.values().length];
public BlockParser() {
this.internalIdAllocator = new IdAllocator(AbstractBlockManager.this.plugin.dataFolderPath().resolve("cache").resolve("custom-block-states.json"));
}
public void addPendingConfigSection(PendingConfigSection section) {
this.pendingConfigSections.add(section);
}
@Nullable
public BlockStateCandidate createVisualBlockCandidate(BlockStateWrapper blockState) {
@@ -302,40 +290,58 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
if (!groups.isEmpty()) {
BlockStateCandidate candidate = new BlockStateCandidate(blockState);
for (AutoStateGroup group : groups) {
getOrCreateBlockStateAllocator(group).addCandidate(candidate);
group.addCandidate(candidate);
}
return candidate;
}
return null;
}
}
private BlockStateAllocator getOrCreateBlockStateAllocator(AutoStateGroup group) {
int index = group.ordinal();
BlockStateAllocator visualBlockStateAllocator = this.visualBlockStateAllocators[index];
if (visualBlockStateAllocator == null) {
visualBlockStateAllocator = new BlockStateAllocator();
this.visualBlockStateAllocators[index] = visualBlockStateAllocator;
}
return visualBlockStateAllocator;
public class BlockParser extends IdSectionConfigParser {
public static final String[] CONFIG_SECTION_NAME = new String[]{"blocks", "block"};
private final IdAllocator internalIdAllocator;
private final VisualBlockStateAllocator visualBlockStateAllocator;
private final List<PendingConfigSection> pendingConfigSections = new ArrayList<>();
public BlockParser(BlockStateCandidate[] candidates) {
this.internalIdAllocator = new IdAllocator(AbstractBlockManager.this.plugin.dataFolderPath().resolve("cache").resolve("custom-block-states.json"));
this.visualBlockStateAllocator = new VisualBlockStateAllocator(AbstractBlockManager.this.plugin.dataFolderPath().resolve("cache").resolve("visual-block-states.json"), candidates, AbstractBlockManager.this::createVanillaBlockState);
}
public void addPendingConfigSection(PendingConfigSection section) {
this.pendingConfigSections.add(section);
}
@Override
public void postProcess() {
this.visualBlockStateAllocator.processPendingAllocations();
try {
this.visualBlockStateAllocator.saveToCache();
} catch (IOException e) {
AbstractBlockManager.this.plugin.logger().warn("Error while saving visual block states allocation", e);
}
this.internalIdAllocator.processPendingAllocations();
try {
this.internalIdAllocator.saveToCache();
} catch (IOException e) {
AbstractBlockManager.this.plugin.logger().warn("Error while saving custom block state allocation", e);
AbstractBlockManager.this.plugin.logger().warn("Error while saving custom block states allocation", e);
}
}
@Override
public void preProcess() {
this.visualBlockStateAllocator.reset();
try {
this.visualBlockStateAllocator.loadFromCache();
} catch (IOException e) {
AbstractBlockManager.this.plugin.logger().warn("Error while loading visual block states allocation cache", e);
}
this.internalIdAllocator.reset(0, Config.serverSideBlocks() - 1);
try {
this.internalIdAllocator.loadFromCache();
} catch (IOException e) {
AbstractBlockManager.this.plugin.logger().warn("Error while loading custom block state allocation cache", e);
AbstractBlockManager.this.plugin.logger().warn("Error while loading custom block states allocation cache", e);
}
for (PendingConfigSection section : this.pendingConfigSections) {
ResourceConfigUtils.runCatching(
@@ -437,9 +443,9 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
}
}
CompletableFutures.allOf(internalIdAllocators).whenComplete((v, t) -> ResourceConfigUtils.runCatching(path, node, () -> {
if (t != null) {
if (t instanceof CompletionException e) {
CompletableFutures.allOf(internalIdAllocators).whenComplete((v1, t1) -> ResourceConfigUtils.runCatching(path, node, () -> {
if (t1 != null) {
if (t1 instanceof CompletionException e) {
Throwable cause = e.getCause();
// 这里不会有conflict了因为之前已经判断过了
if (cause instanceof IdAllocator.IdExhaustedException) {
@@ -449,7 +455,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
return;
}
}
throw new RuntimeException("Unknown error occurred", t);
throw new RuntimeException("Unknown error occurred", t1);
}
for (int i = 0; i < internalIdAllocators.size(); i++) {
@@ -472,110 +478,164 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
);
BlockBehavior blockBehavior = createBlockBehavior(customBlock, MiscUtils.getAsMapList(ResourceConfigUtils.get(section, "behavior", "behaviors")));
// 单状态
Map<String, Map<String, Object>> appearanceConfigs;
Map<String, CompletableFuture<BlockStateWrapper>> futureVisualStates = new HashMap<>();
if (singleState) {
BlockStateWrapper appearanceState = parsePluginFormattedBlockState(ResourceConfigUtils.requireNonEmptyStringOrThrow(stateSection.get("state"), "warning.config.block.state.missing_state"));
this.arrangeModelForStateAndVerify(appearanceState, ResourceConfigUtils.get(stateSection, "model", "models"));
ImmutableBlockState onlyState = states.getFirst();
// 为唯一的状态绑定外观
onlyState.setVanillaBlockState(appearanceState);
parseBlockEntityRender(stateSection.get("entity-renderer")).ifPresent(onlyState::setConstantRenderers);
appearanceConfigs = Map.of("", stateSection);
} else {
BlockStateWrapper anyAppearanceState = null;
Map<String, Object> appearancesSection = ResourceConfigUtils.getAsMap(ResourceConfigUtils.requireNonNullOrThrow(stateSection.get("appearances"), "warning.config.block.state.missing_appearances"), "appearances");
// 也不能为空
if (appearancesSection.isEmpty()) {
throw new LocalizedResourceConfigException("warning.config.block.state.missing_appearances");
}
Map<String, BlockStateAppearance> appearances = Maps.newHashMap();
// 先解析所有的外观
for (Map.Entry<String, Object> entry : appearancesSection.entrySet()) {
Map<String, Object> appearanceSection = ResourceConfigUtils.getAsMap(entry.getValue(), entry.getKey());
// 解析对应的视觉方块
BlockStateWrapper appearanceState = parsePluginFormattedBlockState(ResourceConfigUtils.requireNonEmptyStringOrThrow(appearanceSection.get("state"), "warning.config.block.state.missing_state"));
this.arrangeModelForStateAndVerify(appearanceState, ResourceConfigUtils.get(appearanceSection, "model", "models"));
appearances.put(entry.getKey(), new BlockStateAppearance(appearanceState, parseBlockEntityRender(appearanceSection.get("entity-renderer"))));
if (anyAppearanceState == null) {
anyAppearanceState = appearanceState;
}
}
// 解析变体
Map<String, Object> variantsSection = ResourceConfigUtils.getAsMap(ResourceConfigUtils.requireNonNullOrThrow(stateSection.get("variants"), "warning.config.block.state.missing_variants"), "variants");
for (Map.Entry<String, Object> entry : variantsSection.entrySet()) {
Map<String, Object> variantSection = ResourceConfigUtils.getAsMap(entry.getValue(), entry.getKey());
String variantNBT = entry.getKey();
// 先解析nbt找到需要修改的方块状态
CompoundTag tag = BlockNbtParser.deserialize(variantProvider, variantNBT);
if (tag == null) {
throw new LocalizedResourceConfigException("warning.config.block.state.property.invalid_format", variantNBT);
}
List<ImmutableBlockState> possibleStates = variantProvider.getPossibleStates(tag);
Map<String, Object> anotherSetting = ResourceConfigUtils.getAsMapOrNull(variantSection.get("settings"), "settings");
if (anotherSetting != null) {
for (ImmutableBlockState possibleState : possibleStates) {
possibleState.setSettings(BlockSettings.ofFullCopy(possibleState.settings(), anotherSetting));
}
}
String appearanceName = ResourceConfigUtils.getAsString(variantSection.get("appearance"));
if (appearanceName != null) {
BlockStateAppearance appearance = appearances.get(appearanceName);
if (appearance == null) {
throw new LocalizedResourceConfigException("warning.config.block.state.variant.invalid_appearance", variantNBT, appearanceName);
}
for (ImmutableBlockState possibleState : possibleStates) {
possibleState.setVanillaBlockState(appearance.blockState());
appearance.blockEntityRenderer().ifPresent(possibleState::setConstantRenderers);
}
}
}
// 为没有外观的方块状态填充
for (ImmutableBlockState blockState : states) {
if (blockState.vanillaBlockState() == null) {
blockState.setVanillaBlockState(anyAppearanceState);
}
Map<String, Object> rawAppearancesSection = ResourceConfigUtils.getAsMap(ResourceConfigUtils.requireNonNullOrThrow(stateSection.get("appearances"), "warning.config.block.state.missing_appearances"), "appearances");
appearanceConfigs = new LinkedHashMap<>(4);
for (Map.Entry<String, Object> entry : rawAppearancesSection.entrySet()) {
appearanceConfigs.put(entry.getKey(), ResourceConfigUtils.getAsMap(entry.getValue(), entry.getKey()));
}
}
// 获取方块实体行为
EntityBlockBehavior entityBlockBehavior = blockBehavior.getEntityBehavior();
boolean isEntityBlock = entityBlockBehavior != null;
// 绑定行为
for (ImmutableBlockState state : states) {
if (isEntityBlock) {
state.setBlockEntityType(entityBlockBehavior.blockEntityType());
}
state.setBehavior(blockBehavior);
int internalId = state.customBlockState().registryId();
BlockStateWrapper visualState = state.vanillaBlockState();
int appearanceId = visualState.registryId();
int index = internalId - AbstractBlockManager.this.vanillaBlockStateCount;
AbstractBlockManager.this.immutableBlockStates[index] = state;
AbstractBlockManager.this.blockStateMappings[internalId] = appearanceId;
AbstractBlockManager.this.appearanceToRealState.computeIfAbsent(appearanceId, k -> new IntArrayList()).add(internalId);
AbstractBlockManager.this.tempVisualBlockStatesInUse.add(visualState);
AbstractBlockManager.this.tempVisualBlocksInUse.add(getBlockOwnerId(visualState));
AbstractBlockManager.this.applyPlatformSettings(state);
// generate mod assets
if (Config.generateModAssets()) {
AbstractBlockManager.this.modBlockStateOverrides.put(BlockManager.createCustomBlockKey(index), Optional.ofNullable(AbstractBlockManager.this.tempVanillaBlockStateModels.get(appearanceId))
.orElseGet(() -> {
// 如果未指定模型,说明复用原版模型?但是部分模型是多部位模型,无法使用变体解决问题
// 未来需要靠mod重构彻底解决问题
JsonObject json = new JsonObject();
json.addProperty("model", "minecraft:block/air");
return json;
}));
for (Map.Entry<String, Map<String, Object>> entry : appearanceConfigs.entrySet()) {
Map<String, Object> appearanceSection = entry.getValue();
if (appearanceSection.containsKey("state")) {
String appearanceName = entry.getKey();
futureVisualStates.put(
appearanceName,
this.visualBlockStateAllocator.assignFixedBlockState(appearanceName.isEmpty() ? id.asString() : id.asString() + ":" + appearanceName, parsePluginFormattedBlockState(appearanceSection.get("state").toString()))
);
} else if (stateSection.containsKey("auto-state")) {
String autoStateId = stateSection.get("auto-state").toString();
AutoStateGroup group = AutoStateGroup.byId(autoStateId);
if (group == null) {
throw new LocalizedResourceConfigException("warning.config.block.state.invalid_auto_state", autoStateId, EnumUtils.toString(AutoStateGroup.values()));
}
String appearanceName = entry.getKey();
futureVisualStates.put(
appearanceName,
this.visualBlockStateAllocator.requestAutoState(appearanceName.isEmpty() ? id.asString() : id.asString() + ":" + appearanceName, group)
);
} else {
throw new LocalizedResourceConfigException("warning.config.block.state.missing_state");
}
}
// 一定要到最后再绑定
customBlock.setBehavior(blockBehavior);
holder.bindValue(customBlock);
CompletableFutures.allOf(futureVisualStates.values()).whenComplete((v2, t2) -> {
if (t2 != null) {
if (t2 instanceof CompletionException e) {
Throwable cause = e.getCause();
if (cause instanceof VisualBlockStateAllocator.StateExhaustedException exhausted) {
throw new LocalizedResourceConfigException("warning.config.block.state.auto_state.exhausted", exhausted.group().id(), String.valueOf(exhausted.group().candidateCount()));
} else {
Debugger.BLOCK.warn(() -> "Unknown error while allocating visual block state.", cause);
return;
}
}
throw new RuntimeException("Unknown error occurred", t2);
}
// 添加方块
AbstractBlockManager.this.byId.put(customBlock.id(), customBlock);
BlockStateAppearance anyAppearance = null;
Map<String, BlockStateAppearance> appearances = new HashMap<>();
for (Map.Entry<String, Map<String, Object>> entry : appearanceConfigs.entrySet()) {
String appearanceName = entry.getKey();
Map<String, Object> appearanceSection = entry.getValue();
BlockStateWrapper visualBlockState;
try {
visualBlockState = futureVisualStates.get(appearanceName).get();
} catch (InterruptedException | ExecutionException e) {
AbstractBlockManager.this.plugin.logger().warn("Interrupted while allocating visual block state for block " + id.asString(), e);
return;
}
this.arrangeModelForStateAndVerify(visualBlockState, ResourceConfigUtils.get(appearanceSection, "model", "models"));
BlockStateAppearance blockStateAppearance = new BlockStateAppearance(visualBlockState, parseBlockEntityRender(appearanceSection.get("entity-renderer")));
appearances.put(appearanceName, blockStateAppearance);
if (anyAppearance == null) {
anyAppearance = blockStateAppearance;
}
}
// 至少有一个外观吧
Objects.requireNonNull(anyAppearance, "any appearance should not be null");
ExceptionCollector<LocalizedResourceConfigException> exceptionCollector = new ExceptionCollector<>();
if (!singleState) {
Map<String, Object> variantsSection = ResourceConfigUtils.getAsMapOrNull(stateSection.get("variants"), "variants");
if (variantsSection != null) {
for (Map.Entry<String, Object> entry : variantsSection.entrySet()) {
Map<String, Object> variantSection = ResourceConfigUtils.getAsMap(entry.getValue(), entry.getKey());
String variantNBT = entry.getKey();
// 先解析nbt找到需要修改的方块状态
CompoundTag tag = BlockNbtParser.deserialize(variantProvider, variantNBT);
if (tag == null) {
exceptionCollector.add(new LocalizedResourceConfigException("warning.config.block.state.property.invalid_format", variantNBT));
continue;
}
List<ImmutableBlockState> possibleStates = variantProvider.getPossibleStates(tag);
Map<String, Object> anotherSetting = ResourceConfigUtils.getAsMapOrNull(variantSection.get("settings"), "settings");
if (anotherSetting != null) {
for (ImmutableBlockState possibleState : possibleStates) {
possibleState.setSettings(BlockSettings.ofFullCopy(possibleState.settings(), anotherSetting));
}
}
String appearanceName = ResourceConfigUtils.getAsString(variantSection.get("appearance"));
if (appearanceName != null) {
BlockStateAppearance appearance = appearances.get(appearanceName);
if (appearance == null) {
exceptionCollector.add(new LocalizedResourceConfigException("warning.config.block.state.variant.invalid_appearance", variantNBT, appearanceName));
continue;
}
for (ImmutableBlockState possibleState : possibleStates) {
possibleState.setVanillaBlockState(appearance.blockState());
appearance.blockEntityRenderer().ifPresent(possibleState::setConstantRenderers);
}
}
}
}
}
// 获取方块实体行为
EntityBlockBehavior entityBlockBehavior = blockBehavior.getEntityBehavior();
boolean isEntityBlock = entityBlockBehavior != null;
// 绑定行为
for (ImmutableBlockState state : states) {
if (isEntityBlock) {
state.setBlockEntityType(entityBlockBehavior.blockEntityType());
}
state.setBehavior(blockBehavior);
int internalId = state.customBlockState().registryId();
BlockStateWrapper visualState = state.vanillaBlockState();
// 校验,为未绑定外观的强行添加外观
if (visualState == null) {
visualState = anyAppearance.blockState();
state.setVanillaBlockState(visualState);
anyAppearance.blockEntityRenderer().ifPresent(state::setConstantRenderers);
}
int appearanceId = visualState.registryId();
int index = internalId - AbstractBlockManager.this.vanillaBlockStateCount;
AbstractBlockManager.this.immutableBlockStates[index] = state;
AbstractBlockManager.this.blockStateMappings[internalId] = appearanceId;
AbstractBlockManager.this.appearanceToRealState.computeIfAbsent(appearanceId, k -> new IntArrayList()).add(internalId);
AbstractBlockManager.this.tempVisualBlockStatesInUse.add(visualState);
AbstractBlockManager.this.tempVisualBlocksInUse.add(getBlockOwnerId(visualState));
AbstractBlockManager.this.applyPlatformSettings(state);
// generate mod assets
if (Config.generateModAssets()) {
AbstractBlockManager.this.modBlockStateOverrides.put(BlockManager.createCustomBlockKey(index), Optional.ofNullable(AbstractBlockManager.this.tempVanillaBlockStateModels[appearanceId])
.orElseGet(() -> {
// 如果未指定模型,说明复用原版模型?但是部分模型是多部位模型,无法使用变体解决问题
// 未来需要靠mod重构彻底解决问题
JsonObject json = new JsonObject();
json.addProperty("model", "minecraft:block/air");
return json;
}));
}
}
// 一定要到最后再绑定
customBlock.setBehavior(blockBehavior);
holder.bindValue(customBlock);
// 添加方块
AbstractBlockManager.this.byId.put(customBlock.id(), customBlock);
// 抛出次要警告
exceptionCollector.throwIfPresent();
});
}, () -> GsonHelper.get().toJson(section)));
}
@@ -622,12 +682,12 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
// 结合variants
JsonElement combinedVariant = GsonHelper.combine(variants);
Map<String, JsonElement> overrideMap = AbstractBlockManager.this.blockStateOverrides.computeIfAbsent(blockId, k -> new HashMap<>());
AbstractBlockManager.this.tempVanillaBlockStateModels.put(blockStateWrapper.registryId(), combinedVariant);
JsonElement previous = overrideMap.get(propertyNBT);
if (previous != null && !previous.equals(combinedVariant)) {
throw new LocalizedResourceConfigException("warning.config.block.state.model.conflict", GsonHelper.get().toJson(combinedVariant), blockState, GsonHelper.get().toJson(previous));
}
overrideMap.put(propertyNBT, combinedVariant);
AbstractBlockManager.this.tempVanillaBlockStateModels[blockStateWrapper.registryId()] = combinedVariant;
}
private JsonObject parseAppearanceModelSectionAsJson(Map<String, Object> section) {

View File

@@ -23,4 +23,16 @@ public abstract class AbstractBlockStateWrapper implements BlockStateWrapper {
public String toString() {
return this.blockState.toString();
}
@Override
public final boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof BlockStateWrapper that)) return false;
return this.registryId == that.registryId();
}
@Override
public int hashCode() {
return this.registryId;
}
}

View File

@@ -1,6 +1,8 @@
package net.momirealms.craftengine.core.block;
import net.momirealms.craftengine.core.pack.allocator.BlockStateCandidate;
import net.momirealms.craftengine.core.util.Key;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
@@ -9,37 +11,68 @@ import java.util.function.Predicate;
public enum AutoStateGroup {
LEAVES("leaves",
Set.of(BlockKeys.OAK_LEAVES, BlockKeys.SPRUCE_LEAVES, BlockKeys.BIRCH_LEAVES, BlockKeys.JUNGLE_LEAVES, BlockKeys.ACACIA_LEAVES, BlockKeys.DARK_OAK_LEAVES, BlockKeys.MANGROVE_LEAVES, BlockKeys.CHERRY_LEAVES, BlockKeys.PALE_OAK_LEAVES, BlockKeys.AZALEA_LEAVES, BlockKeys.FLOWERING_AZALEA_LEAVES),
(w) -> !(boolean) w.getProperty("waterlogged"), 0
(w) -> !(boolean) w.getProperty("waterlogged")
),
WATERLOGGED_LEAVES(
"waterlogged_leaves",
Set.of(BlockKeys.OAK_LEAVES, BlockKeys.SPRUCE_LEAVES, BlockKeys.BIRCH_LEAVES, BlockKeys.JUNGLE_LEAVES, BlockKeys.ACACIA_LEAVES, BlockKeys.DARK_OAK_LEAVES, BlockKeys.MANGROVE_LEAVES, BlockKeys.CHERRY_LEAVES, BlockKeys.PALE_OAK_LEAVES, BlockKeys.AZALEA_LEAVES, BlockKeys.FLOWERING_AZALEA_LEAVES),
(w) -> w.getProperty("waterlogged"), 0
(w) -> w.getProperty("waterlogged")
),
TRIPWIRE("tripwire", Set.of(BlockKeys.TRIPWIRE), (w) -> true, 1),
LOWER_TRIPWIRE("lower_tripwire", Set.of(BlockKeys.TRIPWIRE), (w) -> w.getProperty("attached"), 0),
HIGHER_TRIPWIRE("higher_tripwire", Set.of(BlockKeys.TRIPWIRE), (w) -> !(boolean) w.getProperty("attached"), 0),
NOTE_BLOCK("note_block", Set.of(BlockKeys.NOTE_BLOCK), (w) -> true, 0),
BROWN_MUSHROOM("brown_mushroom", Set.of(BlockKeys.BROWN_MUSHROOM_BLOCK), (w) -> true, 0),
RED_MUSHROOM("red_mushroom", Set.of(BlockKeys.RED_MUSHROOM_BLOCK), (w) -> true, 0),
MUSHROOM_STEM("mushroom_stem", Set.of(BlockKeys.MUSHROOM_STEM), (w) -> true, 0),
MUSHROOM("mushroom", Set.of(BlockKeys.BROWN_MUSHROOM_BLOCK, BlockKeys.RED_MUSHROOM_BLOCK, BlockKeys.MUSHROOM_STEM), (w) -> true, 1),
SOLID("solid", Set.of(BlockKeys.BROWN_MUSHROOM_BLOCK, BlockKeys.RED_MUSHROOM_BLOCK, BlockKeys.MUSHROOM_STEM, BlockKeys.NOTE_BLOCK), (w) -> true, 2);
LOWER_TRIPWIRE("lower_tripwire", Set.of(BlockKeys.TRIPWIRE), (w) -> w.getProperty("attached")),
HIGHER_TRIPWIRE("higher_tripwire", Set.of(BlockKeys.TRIPWIRE), (w) -> !(boolean) w.getProperty("attached")),
NOTE_BLOCK("note_block", Set.of(BlockKeys.NOTE_BLOCK), (w) -> true),
BROWN_MUSHROOM("brown_mushroom", Set.of(BlockKeys.BROWN_MUSHROOM_BLOCK), (w) -> true),
RED_MUSHROOM("red_mushroom", Set.of(BlockKeys.RED_MUSHROOM_BLOCK), (w) -> true),
MUSHROOM_STEM("mushroom_stem", Set.of(BlockKeys.MUSHROOM_STEM), (w) -> true),
TRIPWIRE("tripwire", Set.of(BlockKeys.TRIPWIRE), (w) -> true),
SUGAR_CANE("sugar_cane", Set.of(BlockKeys.SUGAR_CANE), (w) -> true),
CACTUS("cactus", Set.of(BlockKeys.CACTUS), (w) -> true),
SAPLING("sapling", Set.of(BlockKeys.OAK_SAPLING, BlockKeys.SPRUCE_SAPLING, BlockKeys.BIRCH_SAPLING, BlockKeys.JUNGLE_SAPLING, BlockKeys.ACACIA_SAPLING, BlockKeys.DARK_OAK_SAPLING, BlockKeys.CHERRY_SAPLING, BlockKeys.PALE_OAK_SAPLING), (w) -> true),
MUSHROOM("mushroom", Set.of(BlockKeys.BROWN_MUSHROOM_BLOCK, BlockKeys.RED_MUSHROOM_BLOCK, BlockKeys.MUSHROOM_STEM), (w) -> true),
SOLID("solid", Set.of(BlockKeys.BROWN_MUSHROOM_BLOCK, BlockKeys.RED_MUSHROOM_BLOCK, BlockKeys.MUSHROOM_STEM, BlockKeys.NOTE_BLOCK), (w) -> true);
private final Set<Key> blocks;
private final String id;
private final Predicate<BlockStateWrapper> predicate;
private final int priority;
private final List<BlockStateCandidate> candidates = new ArrayList<>();
private int pointer;
AutoStateGroup(String id, Set<Key> blocks, Predicate<BlockStateWrapper> predicate, int priority) {
AutoStateGroup(String id, Set<Key> blocks, Predicate<BlockStateWrapper> predicate) {
this.id = id;
this.blocks = blocks;
this.predicate = predicate;
this.priority = priority;
}
public int priority() {
return priority;
public void reset() {
this.pointer = 0;
this.candidates.clear();
}
public void addCandidate(@NotNull BlockStateCandidate candidate) {
this.candidates.add(candidate);
}
public int candidateCount() {
return candidates.size();
}
@Nullable
public BlockStateCandidate findNextCandidate() {
while (this.pointer < this.candidates.size()) {
final BlockStateCandidate state = this.candidates.get(this.pointer);
if (!state.isUsed()) {
return state;
}
this.pointer++;
}
return null;
}
public boolean test(BlockStateWrapper state) {
if (!this.blocks.contains(state.ownerId())) {
return false;
}
return this.predicate.test(state);
}
public Set<Key> blocks() {

View File

@@ -7,6 +7,7 @@ import java.util.List;
public final class BlockKeys {
private BlockKeys() {}
public static final Key SUGAR_CANE = Key.of("minecraft:sugar_cane");
public static final Key NOTE_BLOCK = Key.of("minecraft:note_block");
public static final Key TRIPWIRE = Key.of("minecraft:tripwire");
public static final Key CRAFTING_TABLE = Key.of("minecraft:crafting_table");
@@ -262,6 +263,15 @@ public final class BlockKeys {
public static final Key AZALEA_LEAVES = Key.of("minecraft:azalea_leaves");
public static final Key FLOWERING_AZALEA_LEAVES = Key.of("minecraft:flowering_azalea_leaves");
public static final Key OAK_SAPLING = Key.of("minecraft:oak_sapling");
public static final Key SPRUCE_SAPLING = Key.of("minecraft:spruce_sapling");
public static final Key BIRCH_SAPLING = Key.of("minecraft:birch_sapling");
public static final Key JUNGLE_SAPLING = Key.of("minecraft:jungle_sapling");
public static final Key DARK_OAK_SAPLING = Key.of("minecraft:dark_oak_sapling");
public static final Key ACACIA_SAPLING = Key.of("minecraft:acacia_sapling");
public static final Key CHERRY_SAPLING = Key.of("minecraft:cherry_sapling");
public static final Key PALE_OAK_SAPLING = Key.of("minecraft:pale_oak_sapling");
public static final List<Key> WOODEN_TRAPDOORS = List.of(OAK_TRAPDOOR, SPRUCE_TRAPDOOR, BIRCH_TRAPDOOR,
ACACIA_TRAPDOOR, PALE_OAK_TRAPDOOR, DARK_OAK_TRAPDOOR, MANGROVE_TRAPDOOR, JUNGLE_TRAPDOOR);
public static final List<Key> CHERRY_TRAPDOORS = List.of(CHERRY_TRAPDOOR);

View File

@@ -1,8 +1,9 @@
package net.momirealms.craftengine.core.block;
import net.momirealms.craftengine.core.util.Key;
import org.jetbrains.annotations.NotNull;
public interface BlockStateWrapper {
public interface BlockStateWrapper extends Comparable<BlockStateWrapper> {
Object literalObject();
@@ -15,4 +16,9 @@ public interface BlockStateWrapper {
boolean hasProperty(String propertyName);
String getAsString();
@Override
default int compareTo(@NotNull BlockStateWrapper o) {
return Integer.compare(registryId(), o.registryId());
}
}

View File

@@ -80,6 +80,16 @@ public abstract class AbstractFontManager implements FontManager {
return offsetFont;
}
@Override
public Map<Key, BitmapImage> loadedImages() {
return Collections.unmodifiableMap(this.images);
}
@Override
public Map<Key, Emoji> emojis() {
return Collections.unmodifiableMap(this.emojis);
}
@Override
public void unload() {
this.fonts.clear();

View File

@@ -48,6 +48,10 @@ public interface FontManager extends Manageable {
OffsetFont offsetFont();
Map<Key, BitmapImage> loadedImages();
Map<Key, Emoji> emojis();
ConfigParser[] parsers();
default EmojiTextProcessResult replaceMiniMessageEmoji(@NotNull String miniMessage, @Nullable Player player) {

View File

@@ -69,6 +69,14 @@ public abstract class AbstractItemManager<I> extends AbstractModelGenerator impl
ItemDataModifiers.init();
}
public ItemParser itemParser() {
return itemParser;
}
public EquipmentParser equipmentParser() {
return equipmentParser;
}
protected static void registerVanillaItemExtraBehavior(ItemBehavior behavior, Key... items) {
for (Key key : items) {
VANILLA_ITEM_EXTRA_BEHAVIORS.computeIfAbsent(key, k -> new ArrayList<>()).add(behavior);

View File

@@ -5,7 +5,6 @@ import com.google.common.collect.Multimap;
import com.google.common.jimfs.Configuration;
import com.google.common.jimfs.Jimfs;
import com.google.gson.*;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import net.momirealms.craftengine.core.font.BitmapImage;
import net.momirealms.craftengine.core.font.Font;
import net.momirealms.craftengine.core.item.equipment.ComponentBasedEquipment;
@@ -27,7 +26,9 @@ import net.momirealms.craftengine.core.pack.obfuscation.ObfA;
import net.momirealms.craftengine.core.pack.revision.Revision;
import net.momirealms.craftengine.core.pack.revision.Revisions;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.config.*;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.plugin.config.ConfigParser;
import net.momirealms.craftengine.core.plugin.config.StringKeyConstructor;
import net.momirealms.craftengine.core.plugin.locale.I18NData;
import net.momirealms.craftengine.core.plugin.locale.LocalizedException;
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
@@ -615,7 +616,10 @@ public abstract class AbstractPackManager implements PackManager {
long o2 = System.nanoTime();
this.plugin.logger().info("Loaded packs. Took " + String.format("%.2f", ((o2 - o1) / 1_000_000.0)) + " ms");
for (ConfigParser parser : this.sortedParsers) {
if (!predicate.test(parser)) continue;
if (!predicate.test(parser)) {
parser.clear();
continue;
}
long t1 = System.nanoTime();
parser.preProcess();
parser.loadAll();

View File

@@ -1,33 +0,0 @@
package net.momirealms.craftengine.core.pack.allocator;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
public class BlockStateAllocator {
private final List<BlockStateCandidate> blockStates = new ArrayList<>();
private int pointer = 0;
private int max = -1;
public void addCandidate(BlockStateCandidate state) {
this.blockStates.add(state);
this.max = this.blockStates.size() - 1;
}
@Nullable
public BlockStateCandidate findNext() {
while (this.pointer < this.max) {
final BlockStateCandidate state = this.blockStates.get(this.pointer);
if (!state.isUsed()) {
return state;
}
this.pointer++;
}
return null;
}
public void processPendingAllocations() {
}
}

View File

@@ -152,7 +152,7 @@ public class IdAllocator {
* @param shouldRemove 判断是否应该移除的谓词
* @return 被移除的ID数量
*/
public int cleanupUnusedIds(Predicate<String> shouldRemove) {
public List<String> cleanupUnusedIds(Predicate<String> shouldRemove) {
List<String> idsToRemove = new ArrayList<>();
for (String id : this.cachedIdMap.keySet()) {
if (shouldRemove.test(id)) {
@@ -160,15 +160,13 @@ public class IdAllocator {
}
}
int removedCount = 0;
for (String id : idsToRemove) {
Integer removedId = this.cachedIdMap.remove(id);
if (removedId != null && !this.forcedIdMap.containsValue(removedId)) {
this.occupiedIdSet.clear(removedId);
removedCount++;
}
}
return removedCount;
return idsToRemove;
}
/**

View File

@@ -0,0 +1,152 @@
package net.momirealms.craftengine.core.pack.allocator;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import net.momirealms.craftengine.core.block.AutoStateGroup;
import net.momirealms.craftengine.core.block.BlockStateWrapper;
import net.momirealms.craftengine.core.util.FileUtils;
import net.momirealms.craftengine.core.util.GsonHelper;
import net.momirealms.craftengine.core.util.Pair;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
public class VisualBlockStateAllocator {
private final Path cacheFilePath;
private final Map<String, BlockStateWrapper> cachedBlockStates = new HashMap<>();
private final Map<String, Pair<AutoStateGroup, CompletableFuture<BlockStateWrapper>>> pendingAllocations = new LinkedHashMap<>();
@SuppressWarnings("unchecked")
private final List<Pair<String, CompletableFuture<BlockStateWrapper>>>[] pendingAllocationFutures = new List[AutoStateGroup.values().length];
private final BlockStateCandidate[] candidates;
private final Function<String, BlockStateWrapper> factory;
public VisualBlockStateAllocator(Path cacheFilePath, BlockStateCandidate[] candidates, Function<String, BlockStateWrapper> factory) {
this.cacheFilePath = cacheFilePath;
this.candidates = candidates;
this.factory = factory;
}
public void reset() {
Arrays.fill(this.pendingAllocationFutures, new ArrayList<>());
this.cachedBlockStates.clear();
this.pendingAllocations.clear();
}
public CompletableFuture<BlockStateWrapper> assignFixedBlockState(String name, BlockStateWrapper state) {
this.cachedBlockStates.remove(name);
BlockStateCandidate candidate = this.candidates[state.registryId()];
if (candidate != null) {
candidate.setUsed();
}
return CompletableFuture.completedFuture(state);
}
public CompletableFuture<BlockStateWrapper> requestAutoState(String name, AutoStateGroup group) {
CompletableFuture<BlockStateWrapper> future = new CompletableFuture<>();
this.pendingAllocations.put(name, new Pair<>(group, future));
this.pendingAllocationFutures[group.ordinal()].add(Pair.of(name, future));
return future;
}
public void processPendingAllocations() {
// 先处理缓存的
for (Map.Entry<String, BlockStateWrapper> entry : this.cachedBlockStates.entrySet()) {
int registryId = entry.getValue().registryId();
// 检查候选方块是否可用
BlockStateCandidate candidate = this.candidates[registryId];
if (candidate != null) {
// 未被使用
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());
}
// 尽管未被使用,该槽位也应该被占用,以避免被自动分配到
candidate.setUsed();
}
// 被使用了就随他去
}
// 没有候选也随他去
}
this.pendingAllocations.clear();
for (AutoStateGroup group : AutoStateGroup.values()) {
List<Pair<String, CompletableFuture<BlockStateWrapper>>> pendingAllocationFuture = this.pendingAllocationFutures[group.ordinal()];
for (Pair<String, CompletableFuture<BlockStateWrapper>> pair : pendingAllocationFuture) {
BlockStateCandidate nextCandidate = group.findNextCandidate();
if (nextCandidate != null) {
nextCandidate.setUsed();
this.cachedBlockStates.put(pair.left(), nextCandidate.blockState());
pair.right().complete(nextCandidate.blockState());
} else {
pair.right().completeExceptionally(new StateExhaustedException(group));
}
}
}
}
public static class StateExhaustedException extends RuntimeException {
private final AutoStateGroup group;
public StateExhaustedException(AutoStateGroup group) {
this.group = group;
}
public AutoStateGroup group() {
return group;
}
}
/**
* 从文件加载缓存
*/
public void loadFromCache() throws IOException {
if (!Files.exists(this.cacheFilePath)) {
return;
}
JsonElement element = GsonHelper.readJsonFile(this.cacheFilePath);
if (element instanceof JsonObject jsonObject) {
for (Map.Entry<String, JsonElement> entry : jsonObject.entrySet()) {
if (entry.getValue() instanceof JsonPrimitive primitive) {
String id = primitive.getAsString();
BlockStateWrapper state = this.factory.apply(id);
if (state != null) {
this.cachedBlockStates.put(entry.getKey(), state);
}
}
}
}
}
/**
* 保存缓存到文件
*/
public void saveToCache() throws IOException {
// 创建按ID排序的TreeMap
Map<BlockStateWrapper, String> sortedById = new TreeMap<>();
for (Map.Entry<String, BlockStateWrapper> entry : this.cachedBlockStates.entrySet()) {
sortedById.put(entry.getValue(), entry.getKey());
}
// 创建有序的JSON对象
JsonObject sortedJsonObject = new JsonObject();
for (Map.Entry<BlockStateWrapper, String> entry : sortedById.entrySet()) {
sortedJsonObject.addProperty(entry.getValue(), entry.getKey().getAsString());
}
if (sortedJsonObject.isEmpty()) {
if (Files.exists(this.cacheFilePath)) {
Files.delete(this.cacheFilePath);
}
} else {
FileUtils.createDirectoriesSafe(this.cacheFilePath.getParent());
GsonHelper.writeJsonFile(sortedJsonObject, this.cacheFilePath);
}
}
}

View File

@@ -0,0 +1,62 @@
package net.momirealms.craftengine.core.pack.allocator.cache;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.CompletableFuture;
public class AllocationCacheFile<T extends Comparable<T>, A> {
private final Map<String, T> cache;
private final CacheStorage<A> cacheStorage;
private final CacheSerializer<T, A> serializer;
public AllocationCacheFile(CacheStorage<A> cacheStorage, CacheSerializer<T, A> serializer) {
this.cache = new HashMap<>();
this.cacheStorage = cacheStorage;
this.serializer = serializer;
}
public Map<String, T> cache() {
return this.cache;
}
public void clear() {
this.cache.clear();
}
public CompletableFuture<Void> load() {
return this.cacheStorage.load().thenAccept(a -> {
Map<String, T> deserialized = this.serializer.deserialize(a);
this.cache.putAll(deserialized);
});
}
public CompletableFuture<Void> save() {
Map<T, String> sortedById = new TreeMap<>();
for (Map.Entry<String, T> entry : this.cache.entrySet()) {
sortedById.put(entry.getValue(), entry.getKey());
}
return this.cacheStorage.save(this.serializer.serialize(sortedById));
}
public Iterable<Map.Entry<String, T>> entrySet() {
return this.cache.entrySet();
}
public Set<String> keySet() {
return this.cache.keySet();
}
public void put(String name, T newId) {
this.cache.put(name, newId);
}
public T remove(String name) {
return this.cache.remove(name);
}
public T get(String name) {
return this.cache.get(name);
}
}

View File

@@ -0,0 +1,98 @@
package net.momirealms.craftengine.core.pack.allocator.cache;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.CompletableFuture;
public interface CacheFileStorage<A> {
CompletableFuture<A> load();
CompletableFuture<Void> save(A value);
boolean needForceUpdate();
static <A> LocalFileCacheStorage<A> local(Path path, CacheFileType<A> type) {
return new LocalFileCacheStorage<>(path, type);
}
abstract class AbstractRemoteFileCacheStorage<A> implements CacheFileStorage<A> {
@Override
public boolean needForceUpdate() {
return true;
}
}
class LocalFileCacheStorage<A> implements CacheFileStorage<A> {
private final CacheFileType<A> fileType;
private final Path filePath;
private long lastModified = 0L;
public LocalFileCacheStorage(Path filePath, CacheFileType<A> type) {
this.filePath = filePath;
this.fileType = type;
updateLastModified();
}
@Override
public boolean needForceUpdate() {
try {
if (!Files.exists(this.filePath)) {
return this.lastModified != 0L; // 文件被删除了,需要更新
}
long currentModified = Files.getLastModifiedTime(this.filePath).toMillis();
if (currentModified > this.lastModified) {
this.lastModified = currentModified;
return true; // 文件被修改了,需要强制更新
}
return false;
} catch (IOException e) {
// 如果无法读取文件信息,保守起见返回 true 强制更新
return true;
}
}
@Override
public CompletableFuture<A> load() {
if (!Files.exists(this.filePath)) {
this.lastModified = 0L; // 重置最后修改时间
return CompletableFuture.completedFuture(this.fileType.create());
}
try {
A result = this.fileType.read(this.filePath);
updateLastModified(); // 加载成功后更新最后修改时间
return CompletableFuture.completedFuture(result);
} catch (Exception e) {
return CompletableFuture.failedFuture(e);
}
}
@Override
public CompletableFuture<Void> save(A value) {
try {
this.fileType.write(this.filePath, value);
updateLastModified(); // 保存成功后更新最后修改时间
return CompletableFuture.completedFuture(null);
} catch (Exception e) {
return CompletableFuture.failedFuture(e);
}
}
/**
* 更新最后修改时间
*/
private void updateLastModified() {
try {
if (Files.exists(this.filePath)) {
this.lastModified = Files.getLastModifiedTime(filePath).toMillis();
} else {
this.lastModified = 0L;
}
} catch (IOException e) {
this.lastModified = 0L; // 出错时重置
}
}
}
}

View File

@@ -0,0 +1,39 @@
package net.momirealms.craftengine.core.pack.allocator.cache;
import com.google.gson.JsonObject;
import net.momirealms.craftengine.core.util.GsonHelper;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public interface CacheFileType<T> {
JsonCacheFileType JSON = new JsonCacheFileType();
T read(Path path) throws IOException;
void write(Path path, T value) throws IOException;
T create();
class JsonCacheFileType implements CacheFileType<JsonObject> {
@Override
public JsonObject read(Path path) throws IOException {
if (Files.exists(path)) {
return GsonHelper.readJsonFile(path).getAsJsonObject();
}
return new JsonObject();
}
@Override
public void write(Path path, JsonObject value) throws IOException {
GsonHelper.writeJsonFile(value, path);
}
@Override
public JsonObject create() {
return new JsonObject();
}
}
}

View File

@@ -0,0 +1,51 @@
package net.momirealms.craftengine.core.pack.allocator.cache;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import java.util.HashMap;
import java.util.Map;
public interface CacheSerializer<T extends Comparable<T>, A> {
A serialize(Map<T, String> obj);
Map<String, T> deserialize(A obj);
static <T extends Comparable<T>> CacheSerializer<T, JsonObject> json() {
return new JsonSerializer<>();
}
class JsonSerializer<T extends Comparable<T>> implements CacheSerializer<T, JsonObject> {
@Override
public JsonObject serialize(Map<T, String> obj) {
JsonObject jsonObject = new JsonObject();
for (Map.Entry<T, String> entry : obj.entrySet()) {
if (entry.getKey() instanceof Integer i) {
jsonObject.addProperty(entry.getValue(), i);
} else if (entry.getKey() instanceof String s) {
jsonObject.addProperty(entry.getValue(), s);
}
}
return jsonObject;
}
@SuppressWarnings("unchecked")
@Override
public Map<String, T> deserialize(JsonObject obj) {
Map<String, T> map = new HashMap<>();
for (Map.Entry<String, JsonElement> entry : obj.entrySet()) {
if (entry.getValue() instanceof JsonPrimitive primitive) {
if (primitive.isNumber()) {
map.put(entry.getKey(), (T) (Integer) primitive.getAsInt());
} else if (primitive.isString()) {
map.put(entry.getKey(), (T) primitive.getAsString());
}
}
}
return map;
}
}
}

View File

@@ -0,0 +1,32 @@
package net.momirealms.craftengine.core.pack.allocator.cache;
import org.jetbrains.annotations.NotNull;
import java.util.concurrent.CompletableFuture;
public class CacheStorage<A> {
private final CacheFileStorage<A> storage;
private A lastReadValue;
public CacheStorage(CacheFileStorage<A> storage) {
this.storage = storage;
}
public CompletableFuture<Void> save(@NotNull final A value) {
if (!value.equals(this.lastReadValue) || this.storage.needForceUpdate()) {
this.lastReadValue = value;
return this.storage.save(value);
}
return CompletableFuture.completedFuture(null);
}
public CompletableFuture<A> load() {
if (this.lastReadValue != null && !this.storage.needForceUpdate()) {
return CompletableFuture.completedFuture(this.lastReadValue);
}
return this.storage.load().thenApply(a -> {
this.lastReadValue = a;
return a;
});
}
}