mirror of
https://github.com/Samsuik/Sakura.git
synced 2025-12-28 19:29:07 +00:00
Track block changes and level tick scheduler
This commit is contained in:
@@ -43,14 +43,12 @@ public final class EntityMergeHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called every tick and provided the current server tick to remove any unneeded merge history.
|
||||
* Called every 200 ticks and the tick is used remove any unneeded merge history.
|
||||
*
|
||||
* @param tick server tick
|
||||
*/
|
||||
public void expire(int tick) {
|
||||
if (tick % 200 == 0) {
|
||||
this.trackedHistory.expire(tick);
|
||||
}
|
||||
public void expire(long tick) {
|
||||
this.trackedHistory.expire(tick);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -39,7 +39,7 @@ public final class TrackedMergeHistory {
|
||||
return this.historyMap.get(originPosition);
|
||||
}
|
||||
|
||||
public void expire(int tick) {
|
||||
public void expire(long tick) {
|
||||
this.historyMap.values().removeIf(p -> p.expiry().isExpired(tick));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
package me.samsuik.sakura.listener;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
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
|
||||
public final class BlockChangeTracker {
|
||||
private final Long2ObjectMap<List<Listener>> chunkListeners = new Long2ObjectOpenHashMap<>();
|
||||
private final Long2ObjectMap<Listener> identifiersInUse = new Long2ObjectOpenHashMap<>();
|
||||
private final Level level;
|
||||
private long identifier = Long.MIN_VALUE;
|
||||
|
||||
public BlockChangeTracker(Level level) {
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
public long listenForChangesOnce(BiPredicate<BlockState, BlockState> filter, Set<BlockPos> positions, Runnable callback) {
|
||||
LongConsumer singleUseCallback = (identifier) -> {
|
||||
callback.run();
|
||||
this.stopListening(identifier);
|
||||
};
|
||||
return this.listenForChanges(filter, positions, singleUseCallback);
|
||||
}
|
||||
|
||||
public long listenForChanges(BiPredicate<BlockState, BlockState> filter, Set<BlockPos> positions, LongConsumer callback) {
|
||||
long identifier = this.identifier++;
|
||||
Listener listener = new Listener(
|
||||
filter, positions, identifier, callback
|
||||
);
|
||||
for (ChunkPos chunkPos : getChunkPositions(positions)) {
|
||||
this.addListenerToChunk(chunkPos, listener);
|
||||
}
|
||||
this.identifiersInUse.put(identifier, listener);
|
||||
return identifier;
|
||||
}
|
||||
|
||||
public void stopListening(long identifier) {
|
||||
Listener listener = this.identifiersInUse.remove(identifier);
|
||||
for (ChunkPos chunkPos : getChunkPositions(listener.positions())) {
|
||||
this.removeListenerFronChunk(chunkPos, listener);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeListenerFronChunk(ChunkPos chunkPos, Listener listener) {
|
||||
long chunkKey = chunkPos.toLong();
|
||||
List<Listener> listeners = this.chunkListeners.computeIfPresent(chunkKey, (k, present) -> {
|
||||
present.remove(listener);
|
||||
return present.isEmpty() ? null : present;
|
||||
});
|
||||
this.updateListeners(chunkPos, Objects.requireNonNullElse(listeners, Collections.emptyList()));
|
||||
}
|
||||
|
||||
private void addListenerToChunk(ChunkPos chunkPos, Listener listener) {
|
||||
long chunkKey = chunkPos.toLong();
|
||||
List<Listener> listeners = this.chunkListeners.computeIfAbsent(chunkKey, i -> new ArrayList<>());
|
||||
listeners.add(listener);
|
||||
this.updateListeners(chunkPos, listeners);
|
||||
}
|
||||
|
||||
private void updateListeners(ChunkPos chunkPos, List<Listener> listeners) {
|
||||
LevelChunk chunk = ((ServerLevel) this.level).chunkSource.getChunkAtIfLoadedImmediately(chunkPos.x, chunkPos.z);
|
||||
if (chunk != null) {
|
||||
chunk.updateBlockChangeListeners(List.copyOf(listeners));
|
||||
}
|
||||
}
|
||||
|
||||
public List<Listener> getListenersForChunk(ChunkPos chunkPos) {
|
||||
return this.chunkListeners.getOrDefault(chunkPos.toLong(), Collections.emptyList());
|
||||
}
|
||||
|
||||
private static Set<ChunkPos> getChunkPositions(Set<BlockPos> positions) {
|
||||
Set<ChunkPos> chunkPositions = new ObjectOpenHashSet<>();
|
||||
for (BlockPos pos : positions) {
|
||||
chunkPositions.add(new ChunkPos(pos));
|
||||
}
|
||||
return chunkPositions;
|
||||
}
|
||||
|
||||
public record Listener(BiPredicate<BlockState, BlockState> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package me.samsuik.sakura.listener;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
import net.minecraft.world.level.Level;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@NullMarked
|
||||
public final class LevelTickScheduler {
|
||||
private final Int2ObjectMap<List<TickTask>> tickTasks = new Int2ObjectLinkedOpenHashMap<>();
|
||||
private final Object2IntMap<TickTask> taskIntervals = new Object2IntOpenHashMap<>();
|
||||
|
||||
public void registerNewTask(Runnable runnable, int interval) {
|
||||
this.registerNewTask(tick -> runnable.run(), interval);
|
||||
}
|
||||
|
||||
public void registerNewTask(TickTask task, int interval) {
|
||||
int safeInterval = Math.max(interval + 1, 1);
|
||||
this.tickTasks.computeIfAbsent(safeInterval, i -> new ArrayList<>())
|
||||
.add(task);
|
||||
this.taskIntervals.put(task, Math.max(safeInterval + 1, 1));
|
||||
}
|
||||
|
||||
private void runTasks(List<TickTask> tasks, long gameTime) {
|
||||
for (TickTask tickTask : tasks) {
|
||||
tickTask.run(gameTime);
|
||||
}
|
||||
}
|
||||
|
||||
public void levelTick(Level level) {
|
||||
long gameTime = level.getGameTime();
|
||||
for (int interval : this.tickTasks.keySet()) {
|
||||
if (gameTime % interval == 0) {
|
||||
this.runTasks(this.tickTasks.get(interval), gameTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface TickTask {
|
||||
void run(long tick);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user