77import java .util .Set ;
88import java .util .UUID ;
99import java .util .concurrent .CompletableFuture ;
10+ import java .util .concurrent .ConcurrentHashMap ;
1011import java .util .concurrent .atomic .AtomicBoolean ;
1112import java .util .concurrent .atomic .AtomicInteger ;
1213import me .mapacheee .extendedhorizons .chunk .cache .ChunkPacketCacheService ;
@@ -60,6 +61,10 @@ private record ChunkSendRequest(
6061 private final ChunkPacketCacheService chunkPacketCacheService ;
6162 private final FakeChunkRefreshCoordinator refreshCoordinator ;
6263 private final State state ;
64+ private final Map <ChunkPacketCacheService .ChunkKey , CompletableFuture <ClientboundLevelChunkWithLightPacket >>
65+ packetBuildInFlight = new ConcurrentHashMap <>();
66+ private final Map <ChunkPacketCacheService .ChunkKey , Long > unavailableUntilMs =
67+ new ConcurrentHashMap <>();
6368
6469 public FakeChunkDispatchService (
6570 Container <Config > configContainer ,
@@ -137,10 +142,20 @@ private CompletableFuture<Boolean> sendFakeChunk(ChunkSendRequest request, Hooks
137142 if (!hooks .isSessionValid (player , expectedWorldId , expectedEpoch ))
138143 return CompletableFuture .completedFuture (false );
139144 long chunkKey = ChunkPos .asLong (x , z );
145+ ChunkPacketCacheService .ChunkKey cacheKey =
146+ new ChunkPacketCacheService .ChunkKey (expectedWorldId , chunkKey );
147+ Long blockedUntil = unavailableUntilMs .get (cacheKey );
148+ long now = System .currentTimeMillis ();
149+ if (blockedUntil != null && blockedUntil > now ) {
150+ hooks .inc (player .getUniqueId (), "chunk_unavailable_skip" );
151+ hooks .recordChunkLatency (startedNs );
152+ return CompletableFuture .completedFuture (false );
153+ }
140154 if (!chunkPacketCacheService .shouldBypass (expectedWorldId , chunkKey )) {
141155 ClientboundLevelChunkWithLightPacket cached =
142156 chunkPacketCacheService .get (expectedWorldId , chunkKey );
143157 if (cached != null ) {
158+ unavailableUntilMs .remove (cacheKey );
144159 hooks .sendPacketForSession (player , cached , expectedWorldId , expectedEpoch );
145160 if (hooks .isSessionValid (player , expectedWorldId , expectedEpoch )) {
146161 tracker .markChunkSent (x , z );
@@ -151,34 +166,69 @@ private CompletableFuture<Boolean> sendFakeChunk(ChunkSendRequest request, Hooks
151166 }
152167 }
153168 }
154- return world
155- .getChunkAtAsync (x , z , true )
156- .thenCompose (
157- chunk -> {
169+ return getOrBuildPacket (world , expectedWorldId , x , z , chunkKey , hooks )
170+ .thenApply (
171+ packet -> {
172+ if (packet == null ) {
173+ unavailableUntilMs .put (cacheKey , System .currentTimeMillis () + 2000L );
174+ return false ;
175+ }
176+ unavailableUntilMs .remove (cacheKey );
177+ if (!hooks .isSessionValid (player , expectedWorldId , expectedEpoch )) return false ;
178+ hooks .sendPacketForSession (player , packet , expectedWorldId , expectedEpoch );
158179 if (!hooks .isSessionValid (player , expectedWorldId , expectedEpoch )) {
159- return CompletableFuture . completedFuture ( false ) ;
180+ return false ;
160181 }
182+ tracker .markChunkSent (x , z );
183+ refreshCoordinator .addSubscription (player .getUniqueId (), expectedWorldId , chunkKey );
184+ hooks .inc (player .getUniqueId (), "fake_sent" );
185+ return true ;
186+ })
187+ .exceptionally (
188+ e -> {
189+ state
190+ .logger ()
191+ .error ("Failed to send fake chunk {},{} to {}" , x , z , player .getName (), e );
192+ return false ;
193+ })
194+ .whenComplete ((ok , err ) -> hooks .recordChunkLatency (startedNs ));
195+ }
196+
197+ private CompletableFuture <ClientboundLevelChunkWithLightPacket > getOrBuildPacket (
198+ World world , UUID expectedWorldId , int x , int z , long chunkKey , Hooks hooks ) {
199+ if (world == null || expectedWorldId == null || hooks == null ) {
200+ return CompletableFuture .completedFuture (null );
201+ }
202+ ChunkPacketCacheService .ChunkKey key = new ChunkPacketCacheService .ChunkKey (expectedWorldId , chunkKey );
203+ CompletableFuture <ClientboundLevelChunkWithLightPacket > existing = packetBuildInFlight .get (key );
204+ if (existing != null ) return existing ;
205+
206+ CompletableFuture <ClientboundLevelChunkWithLightPacket > promise = new CompletableFuture <>();
207+ CompletableFuture <ClientboundLevelChunkWithLightPacket > raced = packetBuildInFlight .putIfAbsent (key , promise );
208+ if (raced != null ) return raced ;
209+
210+ world
211+ .getChunkAtAsync (x , z , config ().fakeChunksGenerateMissingChunks ())
212+ .thenAccept (
213+ chunk -> {
161214 if (chunk == null ) {
162- hooks . inc ( player . getUniqueId (), "chunk_async_null" );
163- return CompletableFuture . completedFuture ( false ) ;
215+ promise . complete ( null );
216+ return ;
164217 }
165- CompletableFuture <Boolean > future = new CompletableFuture <>();
166218 boolean scheduled =
167219 hooks .runAtChunk (
168220 world ,
169221 x ,
170222 z ,
171223 () -> {
172- if (!state .enabled ().get ()
173- || !hooks .isSessionValid (player , expectedWorldId , expectedEpoch )) {
174- future .complete (false );
224+ if (!state .enabled ().get ()) {
225+ promise .complete (null );
175226 return ;
176227 }
177228 try {
178229 ChunkAccess access = ((CraftChunk ) chunk ).getHandle (ChunkStatus .FULL );
179230 if (!(access instanceof LevelChunk nmsChunk )) {
180- hooks .inc (player .getUniqueId (), "chunk_not_full" );
181- future .complete (false );
231+ promise .complete (null );
182232 return ;
183233 }
184234 LevelLightEngine lightEngine = nmsChunk .getLevel ().getLightEngine ();
@@ -187,42 +237,23 @@ private CompletableFuture<Boolean> sendFakeChunk(ChunkSendRequest request, Hooks
187237 new ClientboundLevelChunkWithLightPacket (
188238 nmsChunk , lightEngine , lightMasks [0 ], lightMasks [1 ], true );
189239 chunkPacketCacheService .put (expectedWorldId , chunkKey , packet );
190- hooks .sendPacketForSession (
191- player , packet , expectedWorldId , expectedEpoch );
192- if (!hooks .isSessionValid (player , expectedWorldId , expectedEpoch )) {
193- future .complete (false );
194- return ;
195- }
196- tracker .markChunkSent (x , z );
197- refreshCoordinator .addSubscription (
198- player .getUniqueId (), expectedWorldId , chunkKey );
199- hooks .inc (player .getUniqueId (), "fake_sent" );
200- future .complete (true );
240+ promise .complete (packet );
201241 } catch (Throwable t ) {
202- state
203- .logger ()
204- .error (
205- "Failed live-chunk packet {},{} for {}" ,
206- x ,
207- z ,
208- player .getName (),
209- t );
210- future .complete (false );
242+ promise .complete (null );
211243 }
212244 });
213245 if (!scheduled ) {
214- future .complete (false );
246+ promise .complete (null );
215247 }
216- return future ;
217248 })
218249 .exceptionally (
219250 e -> {
220- state
221- . logger ()
222- . error ( "Failed to send fake chunk {},{} to {}" , x , z , player . getName (), e );
223- return false ;
224- })
225- . whenComplete (( ok , err ) -> hooks . recordChunkLatency ( startedNs )) ;
251+ promise . complete ( null );
252+ return null ;
253+ } );
254+
255+ promise . whenComplete (( packet , err ) -> packetBuildInFlight . remove ( key , promise ));
256+ return promise ;
226257 }
227258
228259 private BitSet [] getLightMasks (LevelChunk chunk ) {
0 commit comments