Skip to content

Commit 59d5a31

Browse files
committed
feat: add live refresh for fake chunks
1 parent f92ab99 commit 59d5a31

File tree

3 files changed

+66
-0
lines changed

3 files changed

+66
-0
lines changed

src/main/java/me/mapacheee/extendedhorizons/chunk/FakeChunkService.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ public class FakeChunkService {
6969
private final Map<UUID, Integer> lastSentRadius = new ConcurrentHashMap<>();
7070
private final Map<UUID, Integer> lastSentSimulationDistance = new ConcurrentHashMap<>();
7171
private final Map<UUID, Long> lastForcedPlanMs = new ConcurrentHashMap<>();
72+
private final Map<UUID, Long> lastAutoRefreshMs = new ConcurrentHashMap<>();
73+
private final Map<UUID, AtomicInteger> autoRefreshCursor = new ConcurrentHashMap<>();
7274
private final Map<UUID, UUID> lastKnownWorldId = new ConcurrentHashMap<>();
7375
private final Map<UUID, Long> sessionEpoch = new ConcurrentHashMap<>();
7476
private final AtomicLong chunkLoadSamples = new AtomicLong();
@@ -141,6 +143,9 @@ public void onEnable() {
141143
}
142144
}
143145
}
146+
if (tracker != null) {
147+
maybeAutoRefreshSentChunks(world, playerId, tracker);
148+
}
144149
});
145150
}
146151
},
@@ -168,6 +173,8 @@ public void onDisable() {
168173
lastSentRadius.clear();
169174
lastSentSimulationDistance.clear();
170175
lastForcedPlanMs.clear();
176+
lastAutoRefreshMs.clear();
177+
autoRefreshCursor.clear();
171178
lastKnownWorldId.clear();
172179
sessionEpoch.clear();
173180
}
@@ -431,6 +438,34 @@ private void resetPlayerState(UUID playerId, boolean resetTracker) {
431438
lastSentRadius.remove(playerId);
432439
lastSentSimulationDistance.remove(playerId);
433440
lastForcedPlanMs.remove(playerId);
441+
lastAutoRefreshMs.remove(playerId);
442+
autoRefreshCursor.remove(playerId);
443+
}
444+
445+
private void maybeAutoRefreshSentChunks(World world, UUID playerId, PlayerChunkTracker tracker) {
446+
if (!config().autoRefreshEnabled()) return;
447+
if (world == null || playerId == null || tracker == null) return;
448+
Set<Long> sent = tracker.getSentChunks();
449+
if (sent.isEmpty()) return;
450+
long now = System.currentTimeMillis();
451+
long periodMs = config().autoRefreshPeriodMs();
452+
Long last = lastAutoRefreshMs.get(playerId);
453+
if (last != null && now - last < periodMs) return;
454+
455+
List<Long> sentList = new ArrayList<>(sent);
456+
int size = sentList.size();
457+
if (size == 0) return;
458+
459+
int perCycle = Math.min(config().autoRefreshChunksPerCycle(), size);
460+
AtomicInteger cursor = autoRefreshCursor.computeIfAbsent(playerId, k -> new AtomicInteger(0));
461+
for (int i = 0; i < perCycle; i++) {
462+
int idx = Math.floorMod(cursor.getAndIncrement(), size);
463+
long chunkKey = sentList.get(idx);
464+
int cx = ChunkPos.getX(chunkKey);
465+
int cz = ChunkPos.getZ(chunkKey);
466+
handleRealChunkInteraction(world, cx, cz);
467+
}
468+
lastAutoRefreshMs.put(playerId, now);
434469
}
435470

436471
private void refreshChunkForPlayer(Player player, World world, PlayerChunkTracker tracker, int chunkX, int chunkZ, long chunkKey) {

src/main/java/me/mapacheee/extendedhorizons/config/Config.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,21 @@ public long cacheBypassAfterRealInteractionMs() {
6565
return fakeChunks.cache().bypassAfterRealInteractionMs();
6666
}
6767

68+
public boolean autoRefreshEnabled() {
69+
if (fakeChunks == null || fakeChunks.liveRefresh() == null) return true;
70+
return fakeChunks.liveRefresh().enabled();
71+
}
72+
73+
public long autoRefreshPeriodMs() {
74+
if (fakeChunks == null || fakeChunks.liveRefresh() == null) return 2000L;
75+
return Math.max(250L, fakeChunks.liveRefresh().periodMs());
76+
}
77+
78+
public int autoRefreshChunksPerCycle() {
79+
if (fakeChunks == null || fakeChunks.liveRefresh() == null) return 1;
80+
return Math.max(1, fakeChunks.liveRefresh().chunksPerCycle());
81+
}
82+
6883
public int interceptorMaxTargetDistance() {
6984
return packetInterceptor == null ? 32 : packetInterceptor.maxTargetDistance();
7085
}
@@ -90,6 +105,7 @@ public record FakeChunksConfig(
90105
KeepAliveConfig keepalive,
91106
WarmupConfig warmup,
92107
CacheConfig cache,
108+
@Setting("live-refresh") LiveRefreshConfig liveRefresh,
93109
@Setting("safe-square-factor") double safeSquareFactor
94110
) {
95111
@ConfigSerializable
@@ -113,6 +129,14 @@ public record CacheConfig(
113129
) {
114130
}
115131

132+
@ConfigSerializable
133+
public record LiveRefreshConfig(
134+
boolean enabled,
135+
@Setting("period-ms") long periodMs,
136+
@Setting("chunks-per-cycle") int chunksPerCycle
137+
) {
138+
}
139+
116140
}
117141

118142
@ConfigSerializable

src/main/resources/config.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,13 @@ fake-chunks:
3838
max-entries: 1500
3939
# Temporary cache bypass after real-player interaction in a chunk.
4040
bypass-after-real-interaction-ms: 3000
41+
live-refresh:
42+
# Periodically refreshes already-sent fake chunks to sync edits (e.g. WorldEdit) and activity.
43+
enabled: true
44+
# Minimum delay between refresh cycles per player.
45+
period-ms: 2000
46+
# Number of sent fake chunks refreshed per cycle.
47+
chunks-per-cycle: 1
4148
# Internal safe-square factor (0.65 = 65% of server view distance).
4249
# Fake chunks are not forced inside this area.
4350
safe-square-factor: 0.65

0 commit comments

Comments
 (0)