205 lines
7.9 KiB
Java
205 lines
7.9 KiB
Java
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;
|
||
});
|
||
}
|
||
}
|
||
} |