Skip to content

Commit e6d26a7

Browse files
committed
feat(chunk): enhance session management and optimize chunk handling pipeline
1 parent ec4a2f4 commit e6d26a7

File tree

11 files changed

+568
-507
lines changed

11 files changed

+568
-507
lines changed

src/main/java/me/mapacheee/extendedhorizons/fakechunks/FakeChunkOrchestratorService.java

Lines changed: 49 additions & 156 deletions
Original file line numberDiff line numberDiff line change
@@ -4,50 +4,38 @@
44
import com.thewinterframework.service.annotation.Service;
55
import io.netty.channel.Channel;
66
import me.mapacheee.extendedhorizons.config.ConfigFacade;
7-
import me.mapacheee.extendedhorizons.fakechunks.cache.ChunkBuildCacheService;
87
import me.mapacheee.extendedhorizons.fakechunks.dispatch.ChunkDispatchService;
98
import me.mapacheee.extendedhorizons.fakechunks.netty.ChannelInjectionService;
10-
import me.mapacheee.extendedhorizons.fakechunks.planner.ChunkPlannerService;
119
import me.mapacheee.extendedhorizons.fakechunks.session.PlayerSession;
1210
import me.mapacheee.extendedhorizons.fakechunks.session.SessionRegistry;
1311
import net.minecraft.network.protocol.game.ClientboundSetChunkCacheCenterPacket;
1412
import net.minecraft.network.protocol.game.ClientboundSetChunkCacheRadiusPacket;
15-
import net.minecraft.world.level.ChunkPos;
1613
import org.bukkit.Bukkit;
1714
import org.bukkit.World;
1815
import org.bukkit.entity.Player;
19-
import org.bukkit.util.Vector;
20-
21-
import java.util.UUID;
2216

2317
@Service
2418
public final class FakeChunkOrchestratorService {
2519

2620
private final ConfigFacade configFacade;
2721
private final SessionRegistry sessionRegistry;
28-
private final ChunkPlannerService plannerService;
2922
private final ChunkDispatchService dispatchService;
30-
private final ChunkBuildCacheService cacheService;
3123
private final ChannelInjectionService channelInjectionService;
3224

3325
@Inject
3426
public FakeChunkOrchestratorService(
3527
ConfigFacade configFacade,
3628
SessionRegistry sessionRegistry,
37-
ChunkPlannerService plannerService,
3829
ChunkDispatchService dispatchService,
39-
ChunkBuildCacheService cacheService,
4030
ChannelInjectionService channelInjectionService
4131
) {
4232
this.configFacade = configFacade;
4333
this.sessionRegistry = sessionRegistry;
44-
this.plannerService = plannerService;
4534
this.dispatchService = dispatchService;
46-
this.cacheService = cacheService;
4735
this.channelInjectionService = channelInjectionService;
4836
}
4937

50-
public void tickPlayer(Player player) {
38+
public void tickPlayer(Player player, long maxTimePerPlayerNanos) {
5139
if (player == null || !player.isOnline()) {
5240
return;
5341
}
@@ -65,157 +53,63 @@ public void tickPlayer(Player player) {
6553
if (channel == null || !channel.isActive()) {
6654
return;
6755
}
56+
this.channelInjectionService.inject(player, session);
57+
this.channelInjectionService.bindSession(channel, session);
6858

6959
int chunkX = player.getLocation().getBlockX() >> 4;
7060
int chunkZ = player.getLocation().getBlockZ() >> 4;
71-
boolean moved = session.hasChunkChanged(chunkX, chunkZ);
72-
int movementDx = 0;
73-
int movementDz = 0;
74-
if (moved) {
75-
movementDx = chunkX - ChunkPos.getX(session.lastChunkKey());
76-
movementDz = chunkZ - ChunkPos.getZ(session.lastChunkKey());
77-
}
78-
Vector look = player.getLocation().getDirection();
79-
double headingX = look == null ? 0.0d : look.getX();
80-
double headingZ = look == null ? 0.0d : look.getZ();
81-
double headingLenSq = headingX * headingX + headingZ * headingZ;
82-
if (headingLenSq > 1.0E-6d) {
83-
double inv = 1.0d / Math.sqrt(headingLenSq);
84-
headingX *= inv;
85-
headingZ *= inv;
86-
} else {
87-
headingX = 0.0d;
88-
headingZ = 0.0d;
89-
}
90-
this.channelInjectionService.inject(player);
91-
this.syncClientCenter(channel, chunkX, chunkZ);
92-
if (moved) {
93-
session.setLastChunk(chunkX, chunkZ);
94-
// BetterView-like behavior: restart nearby-first iteration when player changes chunk.
95-
session.plannerCursor(0);
96-
}
97-
98-
int targetDistance = this.configFacade.get().targetViewDistance(worldName);
99-
this.syncClientRadius(channel, session, targetDistance);
100-
101-
long now = System.currentTimeMillis();
102-
int queueSize = this.configFacade.get().chunkQueueSize();
103-
int buffered = session.pendingQueue().size() + session.inflightChunks().size();
104-
int refillThreshold = Math.max(6, queueSize / 2);
105-
boolean queueNeedsRefill = buffered < refillThreshold;
106-
boolean starvation = buffered <= 2;
107-
boolean boosted = moved || player.isGliding();
108-
boolean shouldPlan = moved
109-
|| queueNeedsRefill
110-
|| now - session.lastPlanAtMs() >= this.configFacade.get().forcePlanIntervalMs();
111-
112-
int baseAdds = Math.max(2, this.configFacade.get().maxSendPerCycle());
113-
int maxAdds = Math.max(baseAdds, Math.min(queueSize, queueSize - buffered + baseAdds));
114-
if (boosted) {
115-
maxAdds = Math.min(queueSize + 2, maxAdds + 2);
116-
}
61+
int targetDistance = this.resolveClientDistance(player, worldName);
62+
int serverDistance = this.resolveServerDistance(player);
11763

11864
TickSnapshot snapshot = new TickSnapshot(
11965
world,
12066
world.getUID(),
12167
chunkX,
12268
chunkZ,
12369
targetDistance,
124-
this.resolveServerDistance(player),
125-
now,
126-
shouldPlan,
127-
maxAdds,
128-
movementDx,
129-
movementDz,
130-
headingX,
131-
headingZ,
132-
starvation,
133-
boosted,
134-
moved
70+
serverDistance,
71+
System.nanoTime() + Math.max(250_000L, maxTimePerPlayerNanos)
13572
);
13673
this.channelInjectionService.executeOnEventLoop(channel, () -> this.processOnNetty(channel, session, snapshot));
13774
}
13875

13976
private void processOnNetty(Channel channel, PlayerSession session, TickSnapshot snapshot) {
140-
if (snapshot.shouldPlan()) {
141-
this.replan(
142-
channel,
143-
session,
144-
snapshot.chunkX(),
145-
snapshot.chunkZ(),
146-
snapshot.now(),
147-
snapshot.targetDistance(),
148-
snapshot.serverDistance(),
149-
snapshot.maxAdds(),
150-
snapshot.worldId(),
151-
snapshot.movementDx(),
152-
snapshot.movementDz(),
153-
snapshot.headingX(),
154-
snapshot.headingZ(),
155-
snapshot.starvation(),
156-
snapshot.moved()
157-
);
158-
}
159-
this.dispatchService.processQueue(snapshot.world(), channel, session, snapshot.boosted());
160-
this.channelInjectionService.flush(channel);
161-
}
162-
163-
private void replan(
164-
Channel channel,
165-
PlayerSession session,
166-
int chunkX,
167-
int chunkZ,
168-
long now,
169-
int targetDistance,
170-
int serverDistance,
171-
int maxAdds,
172-
UUID worldId,
173-
int movementDx,
174-
int movementDz,
175-
double headingX,
176-
double headingZ,
177-
boolean starvation,
178-
boolean moved
179-
) {
180-
session.serverViewDistance(serverDistance);
181-
ChunkPlannerService.PlanInput input = new ChunkPlannerService.PlanInput(
182-
chunkX,
183-
chunkZ,
184-
targetDistance,
185-
serverDistance,
186-
this.configFacade.get().safeSquareFactor(),
187-
session.plannerCursor(),
188-
Math.max(16, maxAdds),
189-
movementDx,
190-
movementDz,
191-
headingX,
192-
headingZ,
193-
session.sentChunks(),
194-
session.pendingQueue(),
195-
session.queuedChunks(),
196-
session.inflightChunks(),
197-
starvation ? null : chunkKey -> this.cacheService.isTemporarilyUnavailable(worldId, chunkKey)
198-
);
199-
ChunkPlannerService.PlanResult planResult = this.plannerService.build(input);
200-
session.plannerCursor(planResult.nextCursor());
201-
session.lastPlanAtMs(now);
77+
session.setWorld(snapshot.worldId());
78+
session.serverViewDistance(snapshot.serverDistance());
79+
session.moveTo(snapshot.chunkX(), snapshot.chunkZ());
20280

203-
for (Long chunkKey : planResult.chunksToUnload()) {
204-
this.dispatchService.sendUnload(channel, session, chunkKey);
81+
if (!session.initiated()) {
82+
session.initiated(true);
83+
session.setChunkPos(snapshot.chunkX(), snapshot.chunkZ());
20584
}
20685

207-
if (moved) {
208-
session.pendingQueue().clear();
209-
session.queuedChunks().clear();
210-
session.pendingQueue().addAll(planResult.rebuiltQueue());
211-
session.queuedChunks().addAll(planResult.rebuiltQueued());
86+
if (!this.preTick(session, snapshot.targetDistance(), snapshot.serverDistance())) {
87+
this.unloadSessionChunks(channel, session);
88+
this.syncClientRadius(channel, session, snapshot.serverDistance());
89+
session.unloadBvChunks();
90+
return;
21291
}
21392

214-
for (Long chunkKey : planResult.toAdd()) {
215-
if (session.queuedChunks().add(chunkKey)) {
216-
session.pendingQueue().addLast(chunkKey);
93+
this.syncClientCenter(channel, snapshot.chunkX(), snapshot.chunkZ());
94+
this.syncClientRadius(channel, session, snapshot.targetDistance());
95+
this.dispatchService.processQueue(snapshot.world(), channel, session, snapshot.deadlineNanos());
96+
this.channelInjectionService.flush(channel);
97+
}
98+
99+
private boolean preTick(PlayerSession session, int targetDistance, int serverDistance) {
100+
if (targetDistance <= serverDistance) {
101+
if (session.enabled()) {
102+
// Handled by caller when disabling to ensure unload packets are actually sent.
103+
session.enabled(false);
217104
}
105+
return false;
106+
}
107+
108+
if (!session.enabled() || session.distance() != targetDistance) {
109+
session.enabled(true);
110+
session.updateDistance(targetDistance);
218111
}
112+
return true;
219113
}
220114

221115
private void syncClientCenter(Channel channel, int chunkX, int chunkZ) {
@@ -244,16 +138,24 @@ private int resolveServerDistance(Player player) {
244138
return globalDistance;
245139
}
246140

141+
private int resolveClientDistance(Player player, String worldName) {
142+
return Math.max(2, this.configFacade.get().targetViewDistance(worldName));
143+
}
144+
247145
private void clearSessionState(Channel channel, PlayerSession session) {
248146
if (channel == null || session == null) {
249147
return;
250148
}
251-
for (Long chunkKey : session.sentChunks()) {
149+
this.unloadSessionChunks(channel, session);
150+
this.channelInjectionService.writeBypass(channel, new ClientboundSetChunkCacheRadiusPacket(session.serverViewDistance()));
151+
session.unloadBvChunks();
152+
session.clearDispatchState();
153+
}
154+
155+
private void unloadSessionChunks(Channel channel, PlayerSession session) {
156+
for (long chunkKey : session.loadedBvChunkKeys()) {
252157
this.dispatchService.sendUnload(channel, session, chunkKey);
253158
}
254-
session.clearDispatchState();
255-
session.plannerCursor(0);
256-
session.lastPlanAtMs(0L);
257159
}
258160

259161
private record TickSnapshot(
@@ -263,16 +165,7 @@ private record TickSnapshot(
263165
int chunkZ,
264166
int targetDistance,
265167
int serverDistance,
266-
long now,
267-
boolean shouldPlan,
268-
int maxAdds,
269-
int movementDx,
270-
int movementDz,
271-
double headingX,
272-
double headingZ,
273-
boolean starvation,
274-
boolean boosted,
275-
boolean moved
168+
long deadlineNanos
276169
) {
277170
}
278171
}

0 commit comments

Comments
 (0)