feat: 完善CommandNode,使其支持权限控制输出以及兼容管理器初始化方法控制触发完善
Some checks failed
Build and Release / build (push) Failing after 1h59m18s
Build and Release / release (push) Has been cancelled

This commit is contained in:
叁玖领域 2026-03-01 22:34:57 +08:00
parent fc28b6846b
commit 8ebdccc830
9 changed files with 724 additions and 52 deletions

View File

@ -1,6 +1,12 @@
name: Build name: Build and Release
on: [push, pull_request] on:
push:
tags:
- 'v*'
permissions:
contents: write
jobs: jobs:
build: build:
@ -10,7 +16,6 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
fetch-tags: true
- name: Setup JDK 21 - name: Setup JDK 21
uses: actions/setup-java@v4 uses: actions/setup-java@v4
@ -18,8 +23,185 @@ jobs:
java-version: '21' java-version: '21'
distribution: 'temurin' distribution: 'temurin'
- name: Setup Gradle - name: Make gradlew executable
uses: gradle/actions/setup-gradle@v4 run: chmod +x ./gradlew
- name: Build with Gradle - name: Build with Gradle
run: ./gradlew build run: ./gradlew build
- name: Prepare release files
run: |
mkdir -p release-files
cp build/libs/*.jar release-files/ 2>/dev/null || true
echo "准备发布的文件:"
ls -la release-files/
- name: Upload release artifacts
uses: actions/upload-artifact@v4
with:
name: release-files
path: release-files/
retention-days: 7
release:
runs-on: ubuntu-latest
needs: build
if: startsWith(github.ref, 'refs/tags/v')
steps:
- name: Checkout with full history
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: release-files
path: ./dist
- name: Generate CZ-compliant changelog
id: generate_changelog
run: |
CURRENT_TAG="${{ github.ref_name }}"
PREV_TAG=$(git describe --tags --abbrev=0 $(git rev-list --tags --skip=1 --max-count=1) 2>/dev/null || echo "")
# 创建临时文件
TEMP_FILE=$(mktemp)
echo "# 🚀 版本 $CURRENT_TAG 发布" > $TEMP_FILE
echo "" >> $TEMP_FILE
echo "## 📋 变更摘要" >> $TEMP_FILE
echo "" >> $TEMP_FILE
if [ -z "$PREV_TAG" ]; then
echo "### 初始版本发布" >> $TEMP_FILE
echo "" >> $TEMP_FILE
echo "这是项目的第一个正式版本。" >> $TEMP_FILE
echo "" >> $TEMP_FILE
# 获取所有提交并按类型分组
git log --pretty=format:"%s" --reverse | while read -r line; do
echo "- $line" >> $TEMP_FILE
done
else
echo "### 从 $PREV_TAG 到 $CURRENT_TAG 的变更" >> $TEMP_FILE
echo "" >> $TEMP_FILE
# 定义符合CZ规范的提交类型映射
declare -A commit_types
commit_types=(
["✨ 新功能"]="^(feat|feature)(\(.*\))?:"
["🐛 修复"]="^(fix|bugfix)(\(.*\))?:"
["📝 文档"]="^(docs|documentation)(\(.*\))?:"
["🎨 样式"]="^(style)(\(.*\))?:"
["🔨 重构"]="^(refactor)(\(.*\))?:"
["⚡️ 性能"]="^(perf|performance)(\(.*\))?:"
["✅ 测试"]="^(test)(\(.*\))?:"
["🔧 构建"]="^(build)(\(.*\))?:"
["👷 CI"]="^(ci)(\(.*\))?:"
["📦 依赖"]="^(chore|deps)(\(.*\))?:"
["⏪ 回退"]="^(revert)(\(.*\))?:"
)
# 获取所有提交
COMMITS=$(git log --pretty=format:"%s" $PREV_TAG..HEAD)
# 处理每种类型的提交
for type_name in "${!commit_types[@]}"; do
pattern="${commit_types[$type_name]}"
# 提取匹配的提交
matched_commits=$(echo "$COMMITS" | grep -E "$pattern" || true)
if [ -n "$matched_commits" ]; then
echo "#### $type_name" >> $TEMP_FILE
echo "" >> $TEMP_FILE
# 处理每条提交提取scope和subject
echo "$matched_commits" | while read -r commit; do
# 解析scope和subject
if [[ $commit =~ ^[a-z]+\((.*)\):\ (.*) ]]; then
scope="${BASH_REMATCH[1]}"
subject="${BASH_REMATCH[2]}"
echo "- **$scope**: $subject" >> $TEMP_FILE
elif [[ $commit =~ ^[a-z]+:\ (.*) ]]; then
subject="${BASH_REMATCH[1]}"
echo "- $subject" >> $TEMP_FILE
else
echo "- $commit" >> $TEMP_FILE
fi
done
echo "" >> $TEMP_FILE
fi
done
# 处理破坏性变更BREAKING CHANGE
breaking_changes=$(git log --pretty=format:"%b" $PREV_TAG..HEAD | grep -i "BREAKING CHANGE" || true)
if [ -n "$breaking_changes" ]; then
echo "#### ⚠️ 破坏性变更" >> $TEMP_FILE
echo "" >> $TEMP_FILE
echo "$breaking_changes" | while read -r line; do
echo "- $line" >> $TEMP_FILE
done
echo "" >> $TEMP_FILE
fi
# 处理未分类的提交
uncategorized="$COMMITS"
for pattern in "${commit_types[@]}"; do
uncategorized=$(echo "$uncategorized" | grep -v -E "$pattern" || true)
done
if [ -n "$uncategorized" ]; then
echo "#### 📝 其他更改" >> $TEMP_FILE
echo "" >> $TEMP_FILE
echo "$uncategorized" | while read -r commit; do
echo "- $commit" >> $TEMP_FILE
done
echo "" >> $TEMP_FILE
fi
fi
echo "## 📊 统计信息" >> $TEMP_FILE
echo "" >> $TEMP_FILE
if [ -z "$PREV_TAG" ]; then
TOTAL_COMMITS=$(git rev-list --count HEAD)
echo "- 总提交数: $TOTAL_COMMITS" >> $TEMP_FILE
echo "- 首次发布" >> $TEMP_FILE
else
COMMITS=$(git rev-list --count $PREV_TAG..HEAD)
echo "- 本次发布提交数: $COMMITS" >> $TEMP_FILE
echo "- 上一个版本: $PREV_TAG" >> $TEMP_FILE
fi
echo "- 发布日期: $(date '+%Y年%m月%d日')" >> $TEMP_FILE
echo "- 当前版本: $CURRENT_TAG" >> $TEMP_FILE
echo "" >> $TEMP_FILE
echo "---" >> $TEMP_FILE
echo "" >> $TEMP_FILE
echo "### 📜 详细提交历史" >> $TEMP_FILE
echo "" >> $TEMP_FILE
# 显示所有提交的详细列表包含scope信息
if [ -z "$PREV_TAG" ]; then
git log --pretty=format:"- **%h** %s - %an (%ad)" --date=short --reverse | head -100 >> $TEMP_FILE
else
git log --pretty=format:"- **%h** %s - %an (%ad)" --date=short $PREV_TAG..HEAD | head -100 >> $TEMP_FILE
fi
# 将文件内容输出到变量
CHANGELOG_CONTENT=$(cat $TEMP_FILE)
echo "changelog<<EOF" >> $GITHUB_OUTPUT
echo "$CHANGELOG_CONTENT" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Create Release
uses: ncipollo/release-action@v1
with:
artifacts: "dist/*.jar"
tag: ${{ github.ref_name }}
name: "版本 ${{ github.ref_name }}"
body: ${{ steps.generate_changelog.outputs.changelog }}
draft: false
prerelease: false

View File

@ -2,3 +2,21 @@
**Lib39** is a general-purpose dependency library for Minecraft mods. **Lib39** is a general-purpose dependency library for Minecraft mods.
It provides utility methods and core functionality that other mods can build upon. It provides utility methods and core functionality that other mods can build upon.
### How to implementation?
**In repositories:**
```groovy
maven {
name = "LTD Maven"
url = "https://nexus.bot.leisuretimedock.top/repository/maven-public/"
}
```
**In dependencies:**
```groovy
dependencies {
implementation("top.r3944realms.lib39:lib39:1.20.1-0.4.1")
}
```

View File

@ -33,7 +33,7 @@ mod_name=3944Realms 's Lib Mod
# The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default.
mod_license=MIT mod_license=MIT
# The mod version. See https://semver.org/ # The mod version. See https://semver.org/
mod_version=0.3.6 mod_version=0.4.1
# The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository.
# This should match the base package used for the mod sources. # This should match the base package used for the mod sources.
# See https://maven.apache.org/guides/mini/guide-naming-conventions.html # See https://maven.apache.org/guides/mini/guide-naming-conventions.html

View File

@ -1,6 +1,7 @@
package top.r3944realms.lib39.core.command; package top.r3944realms.lib39.core.command;
import net.minecraft.ChatFormatting; import net.minecraft.ChatFormatting;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.*; import net.minecraft.network.chat.*;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -435,8 +436,10 @@ public interface ICommandHelpManager {
private void buildCommandTreeString(@NotNull CommandNode node, private void buildCommandTreeString(@NotNull CommandNode node,
@NotNull String indent, @NotNull String indent,
@Nullable String currentFullPath, @Nullable String currentFullPath,
@NotNull List<MutableComponent> result) { @NotNull List<MutableComponent> result,
CommandSourceStack commandSourceStack) {
boolean isRoot = indent.isEmpty(); boolean isRoot = indent.isEmpty();
if (node.testPermission(commandSourceStack)) {
MutableComponent commandLine = buildCommandLine(node, indent, isRoot, currentFullPath); MutableComponent commandLine = buildCommandLine(node, indent, isRoot, currentFullPath);
result.add(commandLine.append(Component.literal(NEWLINE))); result.add(commandLine.append(Component.literal(NEWLINE)));
@ -449,8 +452,8 @@ public interface ICommandHelpManager {
for (CommandNode child : node.getChildren()) { for (CommandNode child : node.getChildren()) {
// 只顯示有描述的子命令 // 只顯示有描述的子命令
if (!child.getDescription().getString().isEmpty()) { if (!child.getDescription().getString().isEmpty() && node.testPermission(commandSourceStack)) {
buildCommandTreeString(child, childIndent, newFullPath, result); buildCommandTreeString(child, childIndent, newFullPath, result, commandSourceStack);
} }
} }
} else if (shouldShowCollapsedInfo(node)) { } else if (shouldShowCollapsedInfo(node)) {
@ -475,15 +478,16 @@ public interface ICommandHelpManager {
} }
} }
} }
}
/** /**
* 获取命令树的字符串表示 * 获取命令树的字符串表示
* *
* @return 命令树列表 command tree * @return 命令树列表 command tree
*/ */
default List<MutableComponent> getCommandTree() { default List<MutableComponent> getCommandTree(CommandSourceStack commandSourceStack) {
List<MutableComponent> result = new ArrayList<>(); List<MutableComponent> result = new ArrayList<>();
buildCommandTreeString(getRootNode(), "", "", result); buildCommandTreeString(getRootNode(), "", "", result, commandSourceStack);
return result; return result;
} }
@ -535,8 +539,8 @@ public interface ICommandHelpManager {
* *
* @return the mutable component * @return the mutable component
*/ */
default MutableComponent buildCommandTreeHelp() { default MutableComponent buildCommandTreeHelp(CommandSourceStack commandSourceStack) {
List<MutableComponent> commandTree = getCommandTree(); List<MutableComponent> commandTree = getCommandTree(commandSourceStack);
return buildHelpMessage(Component.translatable(Lib39LangKey.Message.HELP_HEADER.getKey(), Component.translatable(getHeadKey())), commandTree); return buildHelpMessage(Component.translatable(Lib39LangKey.Message.HELP_HEADER.getKey(), Component.translatable(getHeadKey())), commandTree);
} }

View File

@ -87,7 +87,7 @@ public interface IHelpCommand {
*/ */
default int handleHelp(@NotNull CommandContext<CommandSourceStack> context) { default int handleHelp(@NotNull CommandContext<CommandSourceStack> context) {
ICommandHelpManager commandHelpManager = getCommandHelpManager(); ICommandHelpManager commandHelpManager = getCommandHelpManager();
MutableComponent helpMessage = commandHelpManager.buildCommandTreeHelp(); MutableComponent helpMessage = commandHelpManager.buildCommandTreeHelp(context.getSource());
sendSuccess(context.getSource(), helpMessage); sendSuccess(context.getSource(), helpMessage);
return 1; return 1;
} }
@ -103,7 +103,7 @@ public interface IHelpCommand {
ICommandHelpManager commandHelpManager = getCommandHelpManager(); ICommandHelpManager commandHelpManager = getCommandHelpManager();
boolean success = commandHelpManager.toggleNodeExpanded(hash); boolean success = commandHelpManager.toggleNodeExpanded(hash);
if (success) { if (success) {
MutableComponent helpMessage = Component.literal("\n".repeat(2)).append(commandHelpManager.buildCommandTreeHelp()); MutableComponent helpMessage = Component.literal("\n".repeat(2)).append(commandHelpManager.buildCommandTreeHelp(context.getSource()));
sendSuccess(context.getSource(), helpMessage); sendSuccess(context.getSource(), helpMessage);
} else if (shouldShowToggleFailed()) { } else if (shouldShowToggleFailed()) {
sendFailure(context.getSource(), Component.translatable(Lib39LangKey.Message.HELP_TOGGLE_FAILED.getKey())); sendFailure(context.getSource(), Component.translatable(Lib39LangKey.Message.HELP_TOGGLE_FAILED.getKey()));

View File

@ -1,5 +1,6 @@
package top.r3944realms.lib39.core.command.model; package top.r3944realms.lib39.core.command.model;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent; import net.minecraft.network.chat.MutableComponent;
import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Contract;
@ -8,6 +9,7 @@ import org.jetbrains.annotations.Nullable;
import top.r3944realms.lib39.core.command.ICommandHelpManager; import top.r3944realms.lib39.core.command.ICommandHelpManager;
import java.util.*; import java.util.*;
import java.util.function.Predicate;
/** /**
* The type Command node. * The type Command node.
@ -18,6 +20,8 @@ public class CommandNode {
private boolean hashValid = false; private boolean hashValid = false;
private final ICommandHelpManager helpManager; private final ICommandHelpManager helpManager;
private final String name; private final String name;
private final Predicate<CommandSourceStack> testPermission;
private static final Predicate<CommandSourceStack> TRUE = i -> true;
private final MutableComponent description; private final MutableComponent description;
private final Map<String, CommandNode> children; private final Map<String, CommandNode> children;
@ -31,6 +35,9 @@ public class CommandNode {
// 展开/闭合状态 // 展开/闭合状态
private boolean expanded = true; private boolean expanded = true;
public boolean testPermission(CommandSourceStack source) {
return testPermission.test(source);
}
/** /**
* Instantiates a new Command node. * Instantiates a new Command node.
* *
@ -40,12 +47,7 @@ public class CommandNode {
* @param isRoot the is root * @param isRoot the is root
*/ */
public CommandNode(ICommandHelpManager helpManager, String name, MutableComponent description, boolean isRoot) { public CommandNode(ICommandHelpManager helpManager, String name, MutableComponent description, boolean isRoot) {
this.name = name; this(helpManager, name, description, isRoot, TRUE);
this.helpManager = helpManager;
this.description = description;
this.children = new TreeMap<>();
this.isRoot = isRoot;
invalidateHash();
} }
/** /**
@ -56,10 +58,42 @@ public class CommandNode {
* @param description the description * @param description the description
*/ */
public CommandNode(ICommandHelpManager helpManager, String name, MutableComponent description) { public CommandNode(ICommandHelpManager helpManager, String name, MutableComponent description) {
this(helpManager, name, description, TRUE);
}
/**
* Instantiates a new Command node.
*
* @param helpManager the help manager
* @param name the name
* @param description the description
* @param isRoot the is root
* @param testPermission test has permission
*/
public CommandNode(ICommandHelpManager helpManager, String name, MutableComponent description, boolean isRoot, Predicate<CommandSourceStack> testPermission) {
this.name = name; this.name = name;
this.helpManager = helpManager; this.helpManager = helpManager;
this.description = description; this.description = description;
this.children = new TreeMap<>(); this.children = new TreeMap<>();
this.isRoot = isRoot;
this.testPermission = testPermission;
invalidateHash();
}
/**
* Instantiates a new Command node.
*
* @param helpManager the help manager
* @param name the name
* @param description the description
* @param testPermission test has permission
*/
public CommandNode(ICommandHelpManager helpManager, String name, MutableComponent description, Predicate<CommandSourceStack> testPermission) {
this.name = name;
this.helpManager = helpManager;
this.description = description;
this.children = new TreeMap<>();
this.testPermission = testPermission;
invalidateHash(); invalidateHash();
} }
@ -467,6 +501,36 @@ public class CommandNode {
return root(name, Component.translatable(translationKey)); return root(name, Component.translatable(translationKey));
} }
/**
* 添加根節點
*
* @param name the name
* @param description the description
* @param testPermission the test permission
* @return the builder
*/
public Builder root(@NotNull String name, @NotNull MutableComponent description, Predicate<CommandSourceStack> testPermission) {
if (root != null) {
throw new IllegalStateException("Root node already set");
}
root = new CommandNode(helpManager, name, description, true, testPermission);
nodeStack.push(root);
return this;
}
/**
* 添加根節點使用翻譯鍵
*
* @param name the name
* @param translationKey the translation key
* @param testPermission the test permission
* @return the builder
*/
public Builder root(@NotNull String name, @NotNull String translationKey, Predicate<CommandSourceStack> testPermission) {
return root(name, Component.translatable(translationKey), testPermission);
}
/** /**
* 進入子節點向下移動 * 進入子節點向下移動
* *
@ -552,6 +616,96 @@ public class CommandNode {
return this; return this;
} }
/**
* 進入子節點向下移動
*
* @param name the name
* @param description the description
* @param testPermission the test permission
* @return the builder
*/
public Builder push(@NotNull String name, @NotNull MutableComponent description, Predicate<CommandSourceStack> testPermission) {
checkBuilt();
if (nodeStack.isEmpty()) {
throw new IllegalStateException("No parent node available. Call root() first.");
}
CommandNode parent = nodeStack.peek();
CommandNode child = new CommandNode(helpManager, name, description, testPermission);
if (!child.isLeaf()) {
child.setExpanded(false);
}
parent.addChild(child);
nodeStack.push(child);
return this;
}
/**
* 進入子節點使用翻譯鍵
*
* @param name the name
* @param translationKey the translation key
* @param testPermission the test permission
* @return the builder
*/
public Builder push(@NotNull String name, @NotNull String translationKey, Predicate<CommandSourceStack> testPermission) {
return push(name, Component.translatable(translationKey), testPermission);
}
/**
* 進入子節點並添加參數
*
* @param name the name
* @param description the description
* @param testPermission the test permission
* @param parameters the parameters
* @return the builder
*/
public Builder pushWithParams(@NotNull String name, @NotNull MutableComponent description,
Predicate<CommandSourceStack> testPermission, @NotNull Parameter... parameters) {
push(name, description, testPermission);
current().addParameters(parameters);
return this;
}
/**
* 進入子節點並添加必填參數
*
* @param name the name
* @param description the description
* @param testPermission the test permission
* @param paramNames the param names
* @return the builder
*/
public Builder pushWithRequiredParams(@NotNull String name, @NotNull MutableComponent description,
Predicate<CommandSourceStack> testPermission, @NotNull String @NotNull ... paramNames) {
push(name, description, testPermission);
for (String paramName : paramNames) {
current().addParameter(paramName, true);
}
return this;
}
/**
* 進入子節點並添加可選參數
*
* @param name the name
* @param description the description
* @param testPermission the test permission
* @param paramNames the param names
* @return the builder
*/
public Builder pushWithOptionalParams(@NotNull String name, @NotNull MutableComponent description,
Predicate<CommandSourceStack> testPermission, @NotNull String @NotNull ... paramNames) {
push(name, description, testPermission);
for (String paramName : paramNames) {
current().addParameter(paramName, false);
}
return this;
}
/** /**
* 返回上一級節點向上移動 * 返回上一級節點向上移動
* *
@ -711,6 +865,119 @@ public class CommandNode {
return branch(name, Component.translatable(translationKey), configurator); return branch(name, Component.translatable(translationKey), configurator);
} }
/**
* 添加一個完整的命令分支鏈式調用
*
* @param name the name
* @param description the description
* @param testPermission the test permission
* @param configurator the configurator
* @return the builder
*/
public Builder branch(@NotNull String name, @NotNull MutableComponent description,
Predicate<CommandSourceStack> testPermission, @NotNull BranchConfigurator configurator) {
push(name, description, testPermission);
configurator.configure(this);
pop();
return this;
}
/**
* 添加一個完整的命令分支使用翻譯鍵
*
* @param name the name
* @param translationKey the translation key
* @param testPermission the test permission
* @param configurator the configurator
* @return the builder
*/
public Builder branch(@NotNull String name, @NotNull String translationKey,
Predicate<CommandSourceStack> testPermission, @NotNull BranchConfigurator configurator) {
return branch(name, Component.translatable(translationKey), testPermission, configurator);
}
/**
* 快速添加葉子節點沒有子節點的節點
*
* @param name the name
* @param description the description
* @param testPermission the test permission
* @param parameters the parameters
* @return the builder
*/
public Builder leaf(@NotNull String name, @NotNull MutableComponent description,
Predicate<CommandSourceStack> testPermission, @NotNull Parameter @NotNull ... parameters) {
push(name, description, testPermission);
if (parameters.length > 0) {
params(parameters);
}
pop();
return this;
}
/**
* 快速添加葉子節點使用翻譯鍵
*
* @param name the name
* @param translationKey the translation key
* @param testPermission the test permission
* @param parameters the parameters
* @return the builder
*/
public Builder leaf(@NotNull String name, @NotNull String translationKey,
Predicate<CommandSourceStack> testPermission, @NotNull Parameter... parameters) {
return leaf(name, Component.translatable(translationKey), testPermission,parameters);
}
/**
* 批量添加多個葉子節點
*
* @param commands the commands
* @param testPermission the test permission
* @return the builder
*/
public Builder leaves(@NotNull Map<String, MutableComponent> commands, Predicate<CommandSourceStack> testPermission) {
for (Map.Entry<String, MutableComponent> entry : commands.entrySet()) {
leaf(entry.getKey(), entry.getValue(), testPermission);
}
return this;
}
/**
* 批量添加多個葉子節點
*
* @param commands the commands
* @param testPermission the test permission
* @return the builder
*/
public Builder leavesT(@NotNull Map<String, String> commands, Predicate<CommandSourceStack> testPermission) {
for (Map.Entry<String, String> entry : commands.entrySet()) {
leaf(entry.getKey(), entry.getValue(), testPermission);
}
return this;
}
/**
* 批量添加多個帶參數的葉子節點
*
* @param commands the commands
* @param testPermission the test permission
* @return the builder
*/
public Builder leavesWithParams(@NotNull Map<String, LeafConfig> commands, Predicate<CommandSourceStack> testPermission) {
for (Map.Entry<String, LeafConfig> entry : commands.entrySet()) {
LeafConfig config = entry.getValue();
push(entry.getKey(), config.description(), testPermission);
if (config.parameters() != null && !config.parameters().isEmpty()) {
for (Parameter param : config.parameters()) {
param(param.name(), param.required());
}
}
pop();
}
return this;
}
/** /**
* 快速添加葉子節點沒有子節點的節點 * 快速添加葉子節點沒有子節點的節點
* *
@ -739,7 +1006,7 @@ public class CommandNode {
*/ */
public Builder leaf(@NotNull String name, @NotNull String translationKey, public Builder leaf(@NotNull String name, @NotNull String translationKey,
@NotNull Parameter... parameters) { @NotNull Parameter... parameters) {
return leaf(name, Component.translatable(translationKey), parameters); return leaf(name, Component.translatable(translationKey) ,parameters);
} }
/** /**

View File

@ -151,7 +151,7 @@ public abstract class CompatManager {
// 初始化所有兼容模块 // 初始化所有兼容模块
for (Map.Entry<ResourceLocation, ICompat> entry : compats.entrySet()) { for (Map.Entry<ResourceLocation, ICompat> entry : compats.entrySet()) {
if (!entry.getValue().isInitialized()) { if (!entry.getValue().isInitialized() && entry.getValue().isModLoaded()) {
try { try {
entry.getValue().initialize(); entry.getValue().initialize();
entry.getValue().setInitialize(true); entry.getValue().setInitialize(true);

View File

@ -51,6 +51,7 @@ public class Lib39Compat implements ICompat {
public void addCommonModListener(@NotNull IEventBus modBus) { public void addCommonModListener(@NotNull IEventBus modBus) {
modBus.addListener(this::onSetUp); modBus.addListener(this::onSetUp);
} }
private void onSetUp (@NotNull FMLCommonSetupEvent event) { private void onSetUp (@NotNull FMLCommonSetupEvent event) {
event.enqueueWork(() -> Lib39.LOGGER.info("Loading Lib39 Compat")); event.enqueueWork(() -> Lib39.LOGGER.info("Loading Lib39 Compat"));
} }

View File

@ -0,0 +1,200 @@
package top.r3944realms.lib39.util;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import net.minecraft.core.BlockPos;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.Level;
import org.jetbrains.annotations.Nullable;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
/**
* 一个对象接口该对象可声称对特定维度中的 BlockPos 拥有唯一所有权
* 这个系统被称为 "uniPos"可确保LevelBlockPos对在同一时间只能被一个对象 "拥有"
* 一次只能被一个对象 "拥有"
*
* <p><b>重要</b>由于使用了 weakValues()实现类必须被其他对象强引用
* 否则会被 GC 清理导致锁自动释放通常这意味着将实现类实例存储在
* 适当的管理器或容器中
* @author sch246
*/
public interface IUniPosOwner {
/**
* 检查特定位置当前是否被 *任意* 对象拥有
*
* @param level 维度 The level (dimension) of the position.
* @param pos 坐标 The position to lock.
* @return true if the position has an owner, false otherwise.
*/
default boolean isLocked(Level level, BlockPos pos) {
return UniPosManager.INSTANCE.getOwner(level, pos) != null;
}
/**
* 获取位置的当前所有者如果存在的话
*
* @param level 维度 The level (dimension) of the position.
* @param pos 坐标 The position to lock.
* @return An Optional containing the owner, or an empty Optional if the position is not owned.
*/
default Optional<IUniPosOwner> getOwner(Level level, BlockPos pos) {
return Optional.ofNullable(UniPosManager.INSTANCE.getOwner(level, pos));
}
/**
* 检查该对象是否可以锁定指定位置
* 如果该位置当前未锁定或者该对象已经是所有者则该值为 true
* 注意此方法不是原子操作仅用于快速检查
* 实际锁定时应使用 tryLock() 并检查返回值
*
* @param level 维度 The level (dimension) of the position.
* @param pos 坐标 The position to lock.
* @return true if this object can claim ownership.
*/
default boolean canLock(Level level, BlockPos pos) {
IUniPosOwner owner = UniPosManager.INSTANCE.getOwner(level, pos);
return owner == null || owner == this;
}
/**
* 尝试对该对象的指定位置声明所有权
* 此操作是原子操作
*
* @param level 维度 The level (dimension) of the position.
* @param pos 坐标 The position to lock.
* @return true if ownership was successfully claimed or was already held by this object,
* false if the position is owned by another object.
*/
default boolean tryLock(Level level, BlockPos pos) {
return UniPosManager.INSTANCE.tryLock(level, pos, this);
}
/**
* 释放指定位置的所有权
* 只有当该对象是当前所有者时此操作才会成功
*
* @param level 位置的级别维度 The level (dimension) of the position.
* @param pos 要解锁的位置 The position to unlock.
* @return true if the lock was successfully removed, false otherwise.
*/
default boolean unLock(Level level, BlockPos pos) {
return UniPosManager.INSTANCE.unLock(level, pos, this);
}
/**
* 续租继续持有锁
* @param level 维度
* @param pos 位置
*/
default void refreshLock(Level level, BlockPos pos) {
UniPosManager.INSTANCE.refreshLock(level, pos, this);
}
}
/**
管理 IUniPosOwner 系统的单例存储
该类是后台实现大多数类不应直接使用
在同一维度键ResourceKey<Level范围内确保任意时刻一个 BlockPos 只能被一个对象拥有
注意锁的作用域是维度键级别因此在服务端同一维度的不同 Level 实例之间共享
缓存以 BlockPos long 值为键 IUniPosOwner 的弱值weak values为值
当所有者不再被强引用时条目会自动移除从而释放锁
该实现面向服务端使用
*/
final class UniPosManager {
public static final UniPosManager INSTANCE = new UniPosManager();
// 顶层映射维度键ResourceKey<Level> -> 每维度的坐标缓存
// 缓存键为 BlockPos long 值为 IUniPosOwner 的弱值weak values
// 使用维度键确保同一维度的不同 Level 实例在服务端共享同一套锁
// 使用弱值确保当所有者被 GC 锁能自动释放
private final ConcurrentMap<ResourceKey<Level>, Cache<Long, IUniPosOwner>> dimensionLocks;
private UniPosManager() {
this.dimensionLocks = new ConcurrentHashMap<>();
}
/**
获取或创建特定维度键对应的 Cache
Cache 使用 BlockPos long 值作为键IUniPosOwner 的弱值作为值
ResourceKey<Level> 分隔缓存因此同一维度的不同 Level 实例在服务端共享同一套锁
computeIfAbsent 是原子操作保证线程安全地获取或创建 Cache
*/
private Cache<Long, IUniPosOwner> getDimensionCache(Level level) {
// computeIfAbsent 是原子操作保证线程安全地获取或创建 Cache
return dimensionLocks.computeIfAbsent(level.dimension(), k ->
CacheBuilder.newBuilder()
.expireAfterWrite(5, TimeUnit.SECONDS)
.build()
);
}
/**
* 获取位置的当前所有者
*
* @param level 维度
* @param pos 坐标
* @return 如果存在所有者则返回所有者对象否则返回 null
*/
@Nullable
public IUniPosOwner getOwner(Level level, BlockPos pos) {
return getDimensionCache(level).getIfPresent(pos.asLong());
}
/**
* 尝试为指定位置设置所有者
* 如果锁已被同一所有者获得或持有
* 此操作是原子操作避免竞态条件
*
* @param level 维度
* @param pos 坐标
* @param owner 要声明所有权的对象
* @return true if the lock was acquired or already held by the same owner, false otherwise.
*/
public boolean tryLock(Level level, BlockPos pos, IUniPosOwner owner) {
IUniPosOwner existing = getDimensionCache(level).asMap().putIfAbsent(pos.asLong(), owner);
return existing == null || existing == owner;
}
/**
* 解锁一个位置但前提是所提供的所有者是当前所有者
* 此操作是原子操作
*
* @param level 维度
* @param pos 要解锁的位置
* @param owner 尝试解锁的对象
* @return true if the lock was successfully released by this owner.
*/
public boolean unLock(Level level, BlockPos pos, IUniPosOwner owner) {
Cache<Long, IUniPosOwner> cache = dimensionLocks.get(level.dimension());
if (cache == null) {
return false; // 该维度没有锁
}
// 使用 asMap() 提供的原子操作
return cache.asMap().remove(pos.asLong(), owner);
}
/**
* 续租继续持有锁
* @param level 维度
* @param pos 位置
* @param owner 对象
*/
public void refreshLock(Level level, BlockPos pos, IUniPosOwner owner) {
Cache<Long, IUniPosOwner> cache = dimensionLocks.get(level.dimension());
if (cache != null) {
// 只有当锁的主人是当前 owner 才更新时间
// put 操作会重置 expireAfterWrite 的计时器
cache.asMap().computeIfPresent(pos.asLong(), (k, v) -> {
if (v == owner) return owner;
return v;
});
}
}
}