mirror of
https://github.com/Samsuik/Sakura.git
synced 2025-12-28 19:29:07 +00:00
Reimplement vanilla and eigencraft redstone cache
This commit is contained in:
@@ -14,7 +14,6 @@ import me.samsuik.sakura.physics.PhysicsVersion;
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.entity.item.FallingBlockEntity;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
|
||||
@@ -165,7 +165,7 @@ public final class LocalConfigManager implements LocalStorageHandler {
|
||||
|
||||
public synchronized LocalValueConfig config(BlockPos position) {
|
||||
long gameTime = this.level.getGameTime();
|
||||
long ticks = this.expirationTick - gameTime;
|
||||
long ticks = gameTime - this.expirationTick;
|
||||
if (ticks >= CONFIG_CACHE_EXPIRATION / 3) {
|
||||
this.chunkConfigCache.values().removeIf(pair -> pair.value().isExpired(gameTime));
|
||||
this.expirationTick = gameTime;
|
||||
|
||||
@@ -12,7 +12,6 @@ import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.LongConsumer;
|
||||
|
||||
@NullMarked
|
||||
@@ -26,7 +25,7 @@ public final class BlockChangeTracker {
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
public long listenForChangesOnce(BiPredicate<BlockState, BlockState> filter, Set<BlockPos> positions, Runnable callback) {
|
||||
public long listenForChangesOnce(BlockChangeFilter filter, Set<BlockPos> positions, Runnable callback) {
|
||||
LongConsumer singleUseCallback = (identifier) -> {
|
||||
callback.run();
|
||||
this.stopListening(identifier);
|
||||
@@ -34,7 +33,7 @@ public final class BlockChangeTracker {
|
||||
return this.listenForChanges(filter, positions, singleUseCallback);
|
||||
}
|
||||
|
||||
public long listenForChanges(BiPredicate<BlockState, BlockState> filter, Set<BlockPos> positions, LongConsumer callback) {
|
||||
public long listenForChanges(BlockChangeFilter filter, Set<BlockPos> positions, LongConsumer callback) {
|
||||
long identifier = this.identifier++;
|
||||
Listener listener = new Listener(
|
||||
filter, positions, identifier, callback
|
||||
@@ -48,8 +47,10 @@ public final class BlockChangeTracker {
|
||||
|
||||
public void stopListening(long identifier) {
|
||||
Listener listener = this.identifiersInUse.remove(identifier);
|
||||
for (ChunkPos chunkPos : getChunkPositions(listener.positions())) {
|
||||
this.removeListenerFronChunk(chunkPos, listener);
|
||||
if (listener != null) {
|
||||
for (ChunkPos chunkPos : getChunkPositions(listener.positions())) {
|
||||
this.removeListenerFronChunk(chunkPos, listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,14 +89,18 @@ public final class BlockChangeTracker {
|
||||
return chunkPositions;
|
||||
}
|
||||
|
||||
public record Listener(BiPredicate<BlockState, BlockState> filter, Set<BlockPos> positions,
|
||||
public interface BlockChangeFilter {
|
||||
boolean test(Level level, BlockPos pos, BlockState newBlock, BlockState oldBlock);
|
||||
}
|
||||
|
||||
public record Listener(BlockChangeFilter filter, Set<BlockPos> positions,
|
||||
long identifier, LongConsumer callback) {
|
||||
public void call() {
|
||||
this.callback.accept(this.identifier);
|
||||
}
|
||||
|
||||
public boolean test(BlockPos pos, BlockState newBlock, BlockState oldBlock) {
|
||||
return this.filter.test(newBlock, oldBlock) && this.positions.contains(pos);
|
||||
public boolean test(Level level, BlockPos pos, BlockState newBlock, BlockState oldBlock) {
|
||||
return this.filter.test(level, pos, newBlock, oldBlock) && this.positions.contains(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
package me.samsuik.sakura.redstone;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||
import it.unimi.dsi.fastutil.objects.*;
|
||||
import me.samsuik.sakura.utils.TickExpiry;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.*;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.redstone.ExperimentalRedstoneUtils;
|
||||
import net.minecraft.world.level.redstone.NeighborUpdater;
|
||||
import net.minecraft.world.level.redstone.Orientation;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
import java.util.BitSet;
|
||||
import java.util.List;
|
||||
|
||||
@NullMarked
|
||||
public final class RedstoneNetwork {
|
||||
private final List<RedstoneWireUpdate> wireUpdates;
|
||||
private final List<BlockPos> updates;
|
||||
private final Object2ObjectMap<BlockPos, RedstoneOriginalPower> originalWirePower;
|
||||
private final LongArrayList listeners = new LongArrayList();
|
||||
private final BitSet redundantUpdates = new BitSet();
|
||||
private final TickExpiry expiry;
|
||||
|
||||
public RedstoneNetwork(List<RedstoneWireUpdate> wireUpdates, List<BlockPos> updates, Object2ObjectMap<BlockPos, RedstoneOriginalPower> originalWirePower, TickExpiry expiry) {
|
||||
this.wireUpdates = new ObjectArrayList<>(wireUpdates);
|
||||
this.updates = new ObjectArrayList<>(updates);
|
||||
this.originalWirePower = new Object2ObjectOpenHashMap<>(originalWirePower);
|
||||
this.expiry = expiry;
|
||||
}
|
||||
|
||||
private static boolean hasRedstoneComponentChanged(Level level, BlockPos pos, BlockState newBlock, BlockState oldBlock) {
|
||||
return newBlock.isRedstoneConductor(level, pos) != oldBlock.isRedstoneConductor(level, pos)
|
||||
|| newBlock.isSignalSource() != oldBlock.isSignalSource();
|
||||
}
|
||||
|
||||
private static boolean hasBlockChanged(Level level, BlockPos pos, BlockState newBlock, BlockState oldBlock) {
|
||||
return newBlock.getBlock() != oldBlock.getBlock();
|
||||
}
|
||||
|
||||
public TickExpiry getExpiry() {
|
||||
return this.expiry;
|
||||
}
|
||||
|
||||
private boolean isRegistered() {
|
||||
return !this.listeners.isEmpty();
|
||||
}
|
||||
|
||||
public boolean hasWire(BlockPos pos) {
|
||||
return this.originalWirePower.containsKey(pos);
|
||||
}
|
||||
|
||||
public void invalidate(Level level) {
|
||||
for (long identifier : this.listeners) {
|
||||
level.blockChangeTracker.stopListening(identifier);
|
||||
}
|
||||
this.listeners.clear();
|
||||
}
|
||||
|
||||
private void markNeighboringWiresForShapeUpdates(BlockPos pos, Object2ObjectMap<BlockPos, RedstoneWireUpdate> wires) {
|
||||
for (Direction direction : NeighborUpdater.UPDATE_ORDER) {
|
||||
BlockPos neighborPos = pos.relative(direction);
|
||||
RedstoneWireUpdate wireUpdate = wires.get(neighborPos);
|
||||
//noinspection ConstantValue
|
||||
if (wireUpdate != null) {
|
||||
wireUpdate.updateShapes();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean prepareAndRegister(Level level) {
|
||||
Object2ObjectLinkedOpenHashMap<BlockPos, RedstoneWireUpdate> processedWires = new Object2ObjectLinkedOpenHashMap<>();
|
||||
for (RedstoneWireUpdate wireUpdate : this.wireUpdates.reversed()) {
|
||||
BlockPos wirePos = wireUpdate.getPosition();
|
||||
//noinspection ConstantValue
|
||||
if (processedWires.putAndMoveToFirst(wirePos, wireUpdate) == null) {
|
||||
// It's possible for the block below the redstone to break while the network is updating
|
||||
BlockState state = level.getBlockState(wirePos);
|
||||
if (state.is(Blocks.PISTON_HEAD)) {
|
||||
return false;
|
||||
}
|
||||
} else if (this.originalWirePower.get(wirePos).firstPower() != wireUpdate.getPower()) {
|
||||
// Filter out wires updates that are not the first and last update
|
||||
// This significantly reduces the amount of updates when unpowering
|
||||
wireUpdate.skipWireUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < this.updates.size(); ++i) {
|
||||
BlockPos updatePos = this.updates.get(i);
|
||||
BlockState state = level.getBlockState(updatePos);
|
||||
|
||||
// Filter out redundant neighbor updates
|
||||
if (state.isAir() || state.liquid() || !state.isSpecialBlock() || processedWires.containsKey(updatePos)) {
|
||||
this.redundantUpdates.set(i);
|
||||
}
|
||||
|
||||
// Look for blocks that actually need shape updates
|
||||
Block block = state.getBlock();
|
||||
if (state.is(Blocks.OBSERVER) || state.liquid() || block instanceof FallingBlock || block instanceof LiquidBlockContainer) {
|
||||
this.markNeighboringWiresForShapeUpdates(updatePos, processedWires);
|
||||
}
|
||||
}
|
||||
|
||||
this.addBlockListeners(level);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void addBlockListeners(Level level) {
|
||||
ObjectOpenHashSet<BlockPos> positions = new ObjectOpenHashSet<>(this.updates);
|
||||
positions.addAll(this.originalWirePower.keySet());
|
||||
|
||||
// Register block change listeners
|
||||
this.listeners.add(level.blockChangeTracker.listenForChangesOnce(
|
||||
RedstoneNetwork::hasRedstoneComponentChanged, positions, () -> this.invalidate(level)
|
||||
));
|
||||
|
||||
this.listeners.add(level.blockChangeTracker.listenForChangesOnce(
|
||||
RedstoneNetwork::hasBlockChanged, positions, this.redundantUpdates::clear
|
||||
));
|
||||
}
|
||||
|
||||
private boolean verifyWiresInNetwork(Level level) {
|
||||
for (Object2ObjectMap.Entry<BlockPos, RedstoneOriginalPower> wireEntry : this.originalWirePower.object2ObjectEntrySet()) {
|
||||
BlockState state = level.getBlockState(wireEntry.getKey());
|
||||
if (!state.is(Blocks.REDSTONE_WIRE)) {
|
||||
this.invalidate(level);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (state.getValue(RedStoneWireBlock.POWER) != wireEntry.getValue().originalPower()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void performUpdates(Level level, Orientation orientation, RedStoneWireBlock wireBlock, int updateFrom, int updateTo) {
|
||||
for (int updateIndex = updateFrom; updateIndex < updateTo; ++updateIndex) {
|
||||
if (this.redundantUpdates.get(updateIndex)) {
|
||||
continue;
|
||||
}
|
||||
BlockPos updatePos = this.updates.get(updateIndex);
|
||||
level.getBlockState(updatePos).handleNeighborChanged(level, updatePos, wireBlock, orientation, false);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean applyFromCache(Level level) {
|
||||
this.expiry.refresh(level.getGameTime());
|
||||
if (!this.isRegistered() || !this.verifyWiresInNetwork(level)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Orientation orientation = ExperimentalRedstoneUtils.initialOrientation(level, null, null);
|
||||
RedStoneWireBlock wireBlock = (RedStoneWireBlock) Blocks.REDSTONE_WIRE;
|
||||
int updateFrom = 0;
|
||||
|
||||
for (RedstoneWireUpdate wireUpdate : this.wireUpdates) {
|
||||
if (wireUpdate.canSkipWireUpdate()) {
|
||||
updateFrom = wireUpdate.getUpdateIndex();
|
||||
continue;
|
||||
}
|
||||
|
||||
int updateTo = wireUpdate.getUpdateIndex();
|
||||
this.performUpdates(level, orientation, wireBlock, updateFrom, updateTo);
|
||||
updateFrom = updateTo;
|
||||
|
||||
BlockPos wirePos = wireUpdate.getPosition();
|
||||
BlockState state = level.getBlockState(wirePos);
|
||||
BlockState newState = state.setValue(RedStoneWireBlock.POWER, wireUpdate.getPower());
|
||||
if (level.setBlock(wirePos, newState, Block.UPDATE_CLIENTS | Block.UPDATE_KNOWN_SHAPE)) {
|
||||
if (wireUpdate.needsShapeUpdate()) {
|
||||
wireBlock.turbo.updateNeighborShapes(level, wirePos, newState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.performUpdates(level, orientation, wireBlock, updateFrom, this.updates.size());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package me.samsuik.sakura.redstone;
|
||||
|
||||
import io.papermc.paper.configuration.WorldConfiguration;
|
||||
import me.samsuik.sakura.configuration.local.LocalValueConfig;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.redstone.Orientation;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
@NullMarked
|
||||
public record RedstoneNetworkSource(WorldConfiguration.Misc.RedstoneImplementation redstoneImplementation,
|
||||
BlockPos position, @Nullable Orientation orientation,
|
||||
int updateDepth, int newPower, int oldPower) {
|
||||
|
||||
public static RedstoneNetworkSource createNetworkSource(Level level, BlockPos pos, @Nullable Orientation orientation, int newPower, int oldPower) {
|
||||
LocalValueConfig localConfig = level.localConfig().config(pos);
|
||||
WorldConfiguration.Misc.RedstoneImplementation redstoneImplementation = localConfig.redstoneImplementation;
|
||||
int updateDepth = level.neighborUpdater.getUpdateDepth();
|
||||
return new RedstoneNetworkSource(redstoneImplementation, pos, orientation, updateDepth, newPower, oldPower);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package me.samsuik.sakura.redstone;
|
||||
|
||||
public record RedstoneOriginalPower(int originalPower, int firstPower) {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package me.samsuik.sakura.redstone;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.*;
|
||||
import me.samsuik.sakura.utils.TickExpiry;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.redstone.NeighborUpdater;
|
||||
import net.minecraft.world.level.redstone.Orientation;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@NullMarked
|
||||
public final class RedstoneWireCache {
|
||||
private final Map<RedstoneNetworkSource, RedstoneNetwork> networkCache = new Object2ObjectOpenHashMap<>();
|
||||
private @Nullable RedstoneNetworkSource networkSource;
|
||||
private final List<RedstoneWireUpdate> wireUpdates = new ObjectArrayList<>();
|
||||
private final List<BlockPos> updates = new ObjectArrayList<>();
|
||||
private final Object2ObjectMap<BlockPos, RedstoneOriginalPower> originalWirePower = new Object2ObjectOpenHashMap<>();
|
||||
private final Level level;
|
||||
private @Nullable RedstoneNetwork updatingNetwork;
|
||||
|
||||
public RedstoneWireCache(Level level) {
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
public boolean isWireUpdating(BlockPos pos) {
|
||||
return this.updatingNetwork != null && this.updatingNetwork.hasWire(pos);
|
||||
}
|
||||
|
||||
private boolean isTrackingWireUpdates() {
|
||||
return this.networkSource != null;
|
||||
}
|
||||
|
||||
public boolean tryApplyFromCache(BlockPos pos, @Nullable Orientation orientation, int newPower, int oldPower) {
|
||||
if (!this.isTrackingWireUpdates()) {
|
||||
if (this.updatingNetwork != null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
RedstoneNetworkSource networkSource = RedstoneNetworkSource.createNetworkSource(this.level, pos, orientation, newPower, oldPower);
|
||||
RedstoneNetwork network = this.networkCache.get(networkSource);
|
||||
if (network != null) {
|
||||
try {
|
||||
this.updatingNetwork = network;
|
||||
return network.applyFromCache(this.level);
|
||||
} finally {
|
||||
this.updatingNetwork = null;
|
||||
}
|
||||
}
|
||||
|
||||
this.networkSource = networkSource;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void trackWirePower(BlockPos pos, int newPower, int oldPower) {
|
||||
if (this.isTrackingWireUpdates()) {
|
||||
this.originalWirePower.putIfAbsent(pos, new RedstoneOriginalPower(oldPower, newPower));
|
||||
this.wireUpdates.add(new RedstoneWireUpdate(pos, newPower, this.updates.size()));
|
||||
}
|
||||
}
|
||||
|
||||
public void trackNeighbor(BlockPos pos) {
|
||||
if (this.isTrackingWireUpdates()) {
|
||||
this.updates.add(pos);
|
||||
}
|
||||
}
|
||||
|
||||
public void trackNeighborsAt(BlockPos pos) {
|
||||
if (this.isTrackingWireUpdates()) {
|
||||
for (Direction neighbor : NeighborUpdater.UPDATE_ORDER) {
|
||||
this.updates.add(pos.relative(neighbor));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void expire(long tick) {
|
||||
this.networkCache.values().removeIf(network -> {
|
||||
if (network.getExpiry().isExpired(tick)) {
|
||||
network.invalidate(this.level);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
public void stopTracking() {
|
||||
if (!this.isTrackingWireUpdates()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.wireUpdates.isEmpty()) {
|
||||
// Cache expires if it has not been used in 600 ticks
|
||||
TickExpiry expiration = new TickExpiry(this.level.getGameTime(), 600);
|
||||
RedstoneNetwork redstoneNetwork = new RedstoneNetwork(
|
||||
this.wireUpdates, this.updates, this.originalWirePower, expiration
|
||||
);
|
||||
|
||||
if (redstoneNetwork.prepareAndRegister(this.level)) {
|
||||
this.networkCache.put(this.networkSource, redstoneNetwork);
|
||||
}
|
||||
}
|
||||
|
||||
this.wireUpdates.clear();
|
||||
this.updates.clear();
|
||||
this.originalWirePower.clear();
|
||||
this.networkSource = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package me.samsuik.sakura.redstone;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
@NullMarked
|
||||
public final class RedstoneWireUpdate {
|
||||
private final BlockPos position;
|
||||
private final int power;
|
||||
private final int updateIndex;
|
||||
private boolean updateShape;
|
||||
private boolean skipWire;
|
||||
|
||||
public RedstoneWireUpdate(BlockPos position, int power, int updateIndex) {
|
||||
this.position = position;
|
||||
this.power = power;
|
||||
this.updateIndex = updateIndex;
|
||||
}
|
||||
|
||||
public BlockPos getPosition() {
|
||||
return this.position;
|
||||
}
|
||||
|
||||
public int getPower() {
|
||||
return this.power;
|
||||
}
|
||||
|
||||
public int getUpdateIndex() {
|
||||
return this.updateIndex;
|
||||
}
|
||||
|
||||
public boolean needsShapeUpdate() {
|
||||
return this.updateShape;
|
||||
}
|
||||
|
||||
public void updateShapes() {
|
||||
this.updateShape = true;
|
||||
}
|
||||
|
||||
public boolean canSkipWireUpdate() {
|
||||
return this.skipWire;
|
||||
}
|
||||
|
||||
public void skipWireUpdate() {
|
||||
this.skipWire = true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user