44import com .thewinterframework .service .annotation .Service ;
55import io .netty .channel .Channel ;
66import me .mapacheee .extendedhorizons .config .ConfigFacade ;
7- import me .mapacheee .extendedhorizons .fakechunks .cache .ChunkBuildCacheService ;
87import me .mapacheee .extendedhorizons .fakechunks .dispatch .ChunkDispatchService ;
98import me .mapacheee .extendedhorizons .fakechunks .netty .ChannelInjectionService ;
10- import me .mapacheee .extendedhorizons .fakechunks .planner .ChunkPlannerService ;
119import me .mapacheee .extendedhorizons .fakechunks .session .PlayerSession ;
1210import me .mapacheee .extendedhorizons .fakechunks .session .SessionRegistry ;
1311import net .minecraft .network .protocol .game .ClientboundSetChunkCacheCenterPacket ;
1412import net .minecraft .network .protocol .game .ClientboundSetChunkCacheRadiusPacket ;
15- import net .minecraft .world .level .ChunkPos ;
1613import org .bukkit .Bukkit ;
1714import org .bukkit .World ;
1815import org .bukkit .entity .Player ;
19- import org .bukkit .util .Vector ;
20-
21- import java .util .UUID ;
2216
2317@ Service
2418public 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