Lib39/common/src/main/java/top/r3944realms/lib39/util/IUniPosOwner.java
2026-03-14 22:21:18 +08:00

205 lines
7.9 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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"可确保Level、BlockPos对在同一时间只能被一个对象 "拥有"。
* 一次只能被一个对象 "拥有"。
*
* <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 {
/**
* The constant INSTANCE.
*/
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;
});
}
}
}